[gimp-gap] update to ffmpeg-0.7.11, bugfixes, new fetures detail_tracking, blend_fill
- From: Wolfgang Hofer <wolfgangh src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gimp-gap] update to ffmpeg-0.7.11, bugfixes, new fetures detail_tracking, blend_fill
- Date: Sun, 29 Jan 2012 09:40:55 +0000 (UTC)
commit b5804921457ec3bba57f05f4d4592ff77e71960c
Author: Wolfgang Hofer <wolfgangh svn gnome org>
Date: Sun Jan 29 10:35:33 2012 +0100
update to ffmpeg-0.7.11, bugfixes, new fetures detail_tracking, blend_fill
ChangeLog | 166 ++
NEWS | 11 +-
configure.in | 78 +-
docs/reference/txt/gap_gimprc_params.txt | 27 +
extern_libs/README_extern_libs | 19 +-
extern_libs/configure_options_ffmpeg.txt | 2 +-
extern_libs/configure_options_ffmpeg_win32.txt | 2 +-
extern_libs/ffmpeg.tar.gz | Bin 4471231 -> 5415900 bytes
gap/Makefile.am | 28 +
gap/gap_blend_fill_main.c | 1988 ++++++++++++++++++++++++
gap/gap_colordiff.c | 122 ++
gap/gap_colordiff.h | 33 +
gap/gap_detail_align_exec.c | 821 ++++++++++
gap/gap_detail_align_exec.h | 74 +
gap/gap_detail_tracking_exec.c | 1530 ++++++++++++++++++
gap/gap_detail_tracking_exec.h | 119 ++
gap/gap_detail_tracking_main.c | 719 +++++++++
gap/gap_edge_detection.c | 383 +++++
gap/gap_edge_detection.h | 17 +-
gap/gap_image.c | 67 +-
gap/gap_image.h | 2 +
gap/gap_locate.c | 33 +-
gap/gap_locate2.c | 604 +++++++
gap/gap_locate2.h | 96 ++
gap/gap_mov_dialog.h | 5 +-
gap/gap_mov_exec.c | 37 +-
gap/gap_mov_exec.h | 1 +
gap/gap_mov_render.c | 6 +-
gap/gap_mov_xml_par.c | 8 +
gap/gap_pixelrgn.c | 151 ++
gap/gap_pixelrgn.h | 36 +
gap/gap_player_dialog.c | 258 +++-
gap/gap_player_main.c | 4 +
gap/gap_player_main.h | 9 +-
libgapbase/gap_base.c | 68 +-
libgapbase/gap_base.h | 26 +-
libgapvidapi/gap_vid_api_ffmpeg.c | 135 ++-
po/POTFILES.in | 4 +
vid_enc_ffmpeg/gap_enc_ffmpeg_gui.c | 18 +
vid_enc_ffmpeg/gap_enc_ffmpeg_main.c | 51 +-
vid_enc_ffmpeg/gap_enc_ffmpeg_main.h | 17 +-
vid_enc_ffmpeg/gap_enc_ffmpeg_par.c | 13 +
42 files changed, 7711 insertions(+), 77 deletions(-)
---
diff --git a/ChangeLog b/ChangeLog
old mode 100755
new mode 100644
index d7c55d9..9d0e206
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,168 @@
+2012-01-29 Wolfgang Hofer <hof gimp org>
+
+- fixed a bug in the GAP video API that caused a crash when decoding audio.
+ This bug occured when GIMP-GAP was built with ffmpeg confiure options that enabled MMX
+ and ac3 or aac encoded audio was extracted from videofiles.
+ The fix uses an additional aligned buffer (required for proper operation of MMX based
+ audio format conversions)
+ Note that the current implementation of this fix eliminates the performance advantage
+ of MMX for the audio conversion, but enabled MMX is still preferable for video processing.
+
+ The fix is relevant for both ffmpeg-0.6.1 and ffmpeg-0.7.11
+
+- update to ffmpeg-0.7.11 "Peace" that was released on 2012-01-12
+ removed confiure "--enable-libfaad" (that is no longer supported by ffmpeg-0.7.11.)
+
+ The gimp-gap configure.in script now checks for the yasm assembler
+ and automatically adds --disable-yasm configure option when configuring
+ the ffmpeg libraries build.
+ It displays a warning that installation of yasm is recommanded
+ for the ffmpeg build in that case.
+
+
+ The ffmpeg configure call now uses the options:
+ --disable-ffmpeg --disable-ffplay --disable-ffprobe --disable-ffserver
+ because those binaries are not required to build GIMP-GAP
+ and may cause fail of the build in case different librariy versions
+ are available on different locations.
+
+ The ffmpeg build of those binaries failed in my environment with
+ older libx264 installed at /usr/lib
+ and newer version at /usr/local/lib
+ but built successful when building just the static libs without the ffmpeg binaries.
+
+ - compile support for older ffmpeg 0.6 is still there.
+ replace extern_libs/ffmpeg.tar.gz with an older 0.6, or 0.6.1 tarball
+ and remove the extern_libs/ffmpeg directory before
+ running the autoconf.sh script to build gimp-gap with the older versions)
+
+ - #define GAP_USES_OLD_FFMPEG_0_6 when compiling against 0.6 or 0.6.1
+
+ - support new encoder flags: (via preset parameterfile and GUI)
+ epp->codec_FLAG2_INTRA_REFRESH = 0; /* 0: FALSE */
+
+ - new encoder codec parameters (supported via preset parameterfile):
+ gdouble crf_max; // float crf_max;
+ gint32 log_level_offset; // int log_level_offset;
+ gint32 slices; // int slices;
+ gint32 thread_type; // int thread_type;
+ gint32 active_thread_type; // int active_thread_type;
+ gint32 thread_safe_callbacks; // int thread_safe_callbacks;
+ gint64 vbv_delay; // uint64_t vbv_delay;
+ gint32 audio_service_type; // enum AVAudioServiceType audio_service_type;
+
+- new plug-in to fill selected (small) areas by blending surrounding
+ colors to cover pixel errors. Intended to fix videos
+ shot with (my) camera that has some defect sensor pixels.
+
+- new plug-in to for tracking a detail over a sequence of video frames.
+ This plug-in is typically triggered by the Player and records the movement
+ as XML file usable with the MovePath plug-in.
+ Intended to compensate unwanted camera movements.
+
+ implemented alternative locate algorithm that
+ uses gimp regions more efficient than the old one.
+ (runs at least 10 times faster)
+
+ per default all calls of the old procedure
+ are redirected to use the newer implementation.
+ With the gimprc parameter "
+ (gap-locate-details-use-old-algorithm "yes")
+ the old implementation can be enabled
+ -- for more test and compare purpose on further development cycles--)
+
+- new plug-in inspired by the "exact aligner" script.
+
+ This plug-in can operate on XML files (recorded with the detail tracking feature)
+ to align multiple frames when called as filter with the frames modify feature.
+
+
+- the module gap_pixelrgn implements the procedure
+ gap_gimp_pixel_rgns_unref to enable clean escape
+ from loops based on gimp_pixel_rgns_register, gimp_pixel_rgns_process.
+
+ * Usage CODE example:
+ *
+ * for (pr = gimp_pixel_rgns_register (2, &PR1, &PR2);
+ * pr != NULL;
+ * pr = gimp_pixel_rgns_process (pr))
+ * {
+ * ... evaluate pixel data in pixel regions PR1, PR2
+ * ...
+ * if (further evaluation of the remaining tiles is not necessary)
+ * {
+ * // escaping from the loop without the call
+ * // to gap_gimp_pixel_rgns_unref
+ * // leads to memory leaks because of unbalanced tile ref/unref calls.
+ * break;
+ * }
+ * }
+ * if (pr != NULL)
+ * {
+ * gap_gimp_pixel_rgns_unref(pr);
+ * }
+
+ NOTE: A procedure like gap_gimp_pixel_rgns_unref
+ that deals with libgimp internal private structures
+ should better be part of libgimp.
+ (and should be generally available for plug-in development
+ in future gimp releases.)
+ BUT: The GIMP-GAP implementation would still be required for backwards compatibilty
+ with GIMP-2.6.x releases.
+
+- fixed wrong dialog title in the MovePath single frame apply feature
+
+- MovePath rotation Threshold
+ The old move path implementation ignores rotate values between -0.5 and + 0.5 degree.
+ The threshold is intended for better render performance by skipping
+ rotate transformations when angle is nearly 0.
+ The hardcode value 0.5 is too high (especially for larger frame sizes introduced with HD video)
+
+ The new implementation allows to configure this threshold
+ in the range 0.0 upto 1.0 degree.
+ The new default threshold is configurable via gimprc parameter. "video-move-path-rotate-threshold"
+ and now the default is set to 0.015 degree that shall result in smoother rotations.
+ individual rotateThreshold configuration is supported via MovePath XML file.
+ The rotate_threshold value is not exposed in the Dialog.
+
+
+ * NEWS
+ * configure.in
+ * po/POTFILES.in
+ * libgapbase/gap_base.c [.h]
+ * docs/reference/txt/gap_gimprc_params.txt
+
+ * gap/Makefile.am
+ * gap/gap_mov_dialog.h
+ * gap/gap_mov_exec.c [.h]
+ * gap/gap_mov_render.c
+ * gap/gap_mov_xml_par.c
+
+ * gap/gap_image.c [.h]
+ * gap/gap_player_main.c [.h]
+ * gap/gap_player_dialog.c
+ * gap/gap_locate.c
+ * gap/gap_colordiff.c [.h]
+ * gap/gap_edge_detection.c [.h]
+
+ * gap/gap_blend_fill_main.c # new file
+ * gap/gap_detail_align_exec.c [.h] # new files
+ * gap/gap_detail_tracking_exec.c [.h] # new files
+ * gap/gap_detail_tracking_main.c # new file
+ * gap/gap_locate2.c [.h] # new files
+ * gap/gap_pixelrgn.c [.h] # new files
+
+ * extern_libs/ffmpeg.tar.gz # updated to ffmpeg release 0.7.11
+ * extern_libs/README_extern_libs
+ * extern_libs/configure_options_ffmpeg_win32.txt
+ * extern_libs/configure_options_ffmpeg.txt
+
+ * vid_enc_ffmpeg/gap_enc_ffmpeg_gui.c
+ * vid_enc_ffmpeg/gap_enc_ffmpeg_main.c [.h]
+ * vid_enc_ffmpeg/gap_enc_ffmpeg_par.c
+ * libgapvidapi/gap_vid_api_ffmpeg.c
+
+
2011-11-30 Wolfgang Hofer <hof gimp org>
- added option to merge visible layers
@@ -50,6 +215,7 @@
2011-11-23 Wolfgang Hofer <hof gimp org>
+ * gap/Makefile.am
- Foreground selection: fixed parameters for non interactive calls.
- new feature: foreground extraction from current selection.
diff --git a/NEWS b/NEWS
index ca7dfb3..bbe5bc4 100644
--- a/NEWS
+++ b/NEWS
@@ -29,6 +29,15 @@ Here is a short overview whats new in GIMP-GAP-2.7.0:
in a "process one frame per call" style, intended to be called as filter
with the modify frames feature.
+ - new plug-in to fill selected (small) areas by blending surrounding
+ colors to cover pixel errors. Intended to fix images and videos
+ shot with a camera that has some defect sensor pixels or dirty lens.
+
+ - new plug-in for tracking a significant detail in frames.
+ This plug-in is integrated in the Playback and can
+ record movents of the detail as XML parameters for the MovePath feature.
+ The recorded movements are useful to compensate unwanted shaking
+ of videos recorder without a stativ.
- GIMP-GAP now supports speed control of movements and other transitions
via Acceleration characteristic presets. Those presets are available
@@ -71,7 +80,7 @@ Here is a short overview whats new in GIMP-GAP-2.7.0:
- updated gimp-gap video API and ffmpeg-based video encoder
- to support the libraries provided with the ffmpeg-0.6.1 release
+ to support the libraries provided with the ffmpeg-0.7.11 release
This includes various bugfixes related to video de/encode
(but breaks backwards compatibility when seeking positions by frame number
in videofiles that do not start with a keyframe).
diff --git a/configure.in b/configure.in
index 9a0a681..8b70ac6 100644
--- a/configure.in
+++ b/configure.in
@@ -201,6 +201,15 @@ AC_ARG_ENABLE(ff_libbz2,
+dnl check for the yasm assembler program (recommanded for build of ffmpeg libraries avcodec, avformat,...)
+dnl note that ffmpeg-0.7.11 can be built without yasm when explicte configured with --disable-yasm
+dnl but at least display a warning in this case.
+yasm_warn=""
+AC_CHECK_PROG(HAVE_YASM_ASSEMBLER, yasm, yes, no)
+if test "x$HAVE_YASM_ASSEMBLER" = "xno"; then
+ yasm_warn=" the yasm assembler, recommanded to build libavformat, libavcodec, was not found. "
+fi
+
dnl ## libmp3lame lame/lame.h lame_init -lmp3lame -lm
@@ -240,27 +249,31 @@ dnl check for faad library (useful for better ffmpeg audio support)
dnl the check result does not matter unless libavformat is linked or built later on.
FF_LIBFAAD=""
faad_warn=""
-AC_ARG_ENABLE(ff_libfaad,
- [ --disable-ff-libfaad configure libavformat without libfaad])
- if test "x$enable_ff_libfaad" != "xno"; then
- AC_CHECK_LIB(faad, NeAACDecOpen,
- [AC_CHECK_HEADER(faad.h,
- FF_LIBFAAD="-lfaad",
- faad_warn="$NEW_LINE ** faad header file (faad.h) not found (not critical)")],
- faad_warn="faad library (libfaad) not found (not critical)")
-
- dnl additional check for faad1 library (that is also sufficient for building ffmpeg 0.5)
-
- if test "x$FF_LIBFAAD" = "x"; then
- AC_CHECK_LIB(faad, faacDecOpen,
- [AC_CHECK_HEADER(faad.h,
- FF_LIBFAAD="-lfaad",
- faad_warn="$NEW_LINE ** faad header file (faad.h) not found (not critical)")],
- faad_warn="faad library (libfaad) not found (not critical)")
- fi
- fi
-dnl
+dnl ffmpeg-0.7.8 has dropped support for faad library
+dnl TODO remove faad specific stuff after succesful integration of ffmpeg-0.7.8
+
+dnl AC_ARG_ENABLE(ff_libfaad,
+dnl [ --disable-ff-libfaad configure libavformat without libfaad])
+dnl if test "x$enable_ff_libfaad" != "xno"; then
+dnl AC_CHECK_LIB(faad, NeAACDecOpen,
+dnl [AC_CHECK_HEADER(faad.h,
+dnl FF_LIBFAAD="-lfaad",
+dnl faad_warn="$NEW_LINE ** faad header file (faad.h) not found (not critical)")],
+dnl faad_warn="faad library (libfaad) not found (not critical)")
+dnl
+dnl dnl additional check for faad1 library (that is also sufficient for building ffmpeg 0.5)
+dnl
+dnl if test "x$FF_LIBFAAD" = "x"; then
+dnl AC_CHECK_LIB(faad, faacDecOpen,
+dnl [AC_CHECK_HEADER(faad.h,
+dnl FF_LIBFAAD="-lfaad",
+dnl faad_warn="$NEW_LINE ** faad header file (faad.h) not found (not critical)")],
+dnl faad_warn="faad library (libfaad) not found (not critical)")
+dnl fi
+dnl
+dnl fi
+dnl dnl
@@ -270,16 +283,22 @@ dnl ### set X264_REQUIRED_VERSION="0.65" to support ffmpeg-0.5 (condition "X264_
dnl ### set X264_REQUIRED_VERSION="0.85" to support ffmpeg-0.6 (broken mjpeg decoder)
dnl ### set X264_REQUIRED_VERSION="0.98" to support ffmpeg-2010-09-14 snapshot (for testing)
dnl ### new libx264 versions (X264_BUILD >= 93) require to include stdint.h (or inttypes.h)
-dnl ### before including tthe headerfile x264.h
+dnl ### before including the headerfile x264.h
dnl ### therfore we provide this requirement as 4. parameter of the AC_CHECK_HEADER macro.
dnl ### but AC_CHECK_LIB now fails when checking for procedure x264_encoder_open
dnl ### because x264_encoder_open is a define constant that refers to x264_encoder_open_<X264_BUILD>
-dnl ### therefore therefore the check was changed to procedure x264_encoder_close
+dnl ### therefore the check was changed to procedure x264_encoder_close
dnl ### (dont want to AC_CHECK_LIB that works only for one hardcoded version.)
+dnl ### set X264_REQUIRED_VERSION="0.120" to support ffmpeg-0.7.8
+dnl ### note that ffmpeg configure script says that required libx264 version is >= 0.115
+dnl ### but with libx264 0.115 version installed on my development system
+dnl ### the ffmpeg-0.7.8 configure script complained:
+dnl ### ERROR: libx264 version must be >= 0.115.
+dnl ### After this test i updated to X264 0.120 and ffmpeg-0.7.8 configure worked fine without complaining...
dnl the check result does not matter unless libavformat is linked or built later on.
-X264_REQUIRED_VERSION="0.85"
+X264_REQUIRED_VERSION="0.120"
X264_REQUIRED_INC='#ifdef HAVE_STDINT_H
#include <stdint.h>
#endif'
@@ -299,10 +318,11 @@ dnl fi
if test "x$enable_ff_libx264" != "xno"; then
if ! $PKG_CONFIG --atleast-version=$X264_REQUIRED_VERSION x264; then
x264_warn="$NEW_LINE x264 library (libx264) required version $X264_REQUIRED_VERSION not found (not critical, but no open H264 video codec support)"
+ FF_LIBX264=""
else
AC_CHECK_LIB(x264, x264_encoder_close,
[AC_CHECK_HEADER(x264.h,
- FF_LIBX264="-lx264",
+ FF_LIBX264=`$PKG_CONFIG --libs x264`,
x264_warn="$NEW_LINE x264 header file (x264.h) not found (not critical, but no open H264 video codec support)",
$X264_REQUIRED_INC)],
x264_warn="$NEW_LINE x264 library (libx264) not found (not critical, but no open H264 video codec support)")
@@ -548,6 +568,14 @@ INFORMATION: old ffmpeg was moved to $FFMPEG_DIR-OLD
FFMPEG_ENABLE_NONFREE=""
+
+ dnl ffmpeg-0.7.11 recommands build with yasm assembler, but the
+ dnl configure script fails when yasm is not installed and yasm is not diasbled by explicite configure option.
+ if test "x$HAVE_YASM_ASSEMBLER" = "xno"; then
+ FFMPEG_CONFIGURE_OPTIONS="$FFMPEG_CONFIGURE_OPTIONS --disable-yasm"
+ yasm_warn="$yasm_warn ffmpeg congigure option --disable-yasm was used to allow a crippled build. (Installation of yasm is RECOMMANDED)"
+ fi
+
dnl configure ffmpeg bzip2 usage
if test "x$FF_BZIP2" != "x"; then
FFMPEG_CONFIGURE_OPTIONS="$FFMPEG_CONFIGURE_OPTIONS --enable-bzlib"
@@ -1187,4 +1215,4 @@ docs/reference/Makefile
docs/reference/txt/Makefile
])
-AC_MSG_RESULT($frontends_warning $audio_warning $videoapi_warning $moved_old_ffmpeg_warn $moved_old_libmpeg3_warn $vid_ffmpeg_warning $vid_mpeg3_warning $vid_quicktime_warning $vid_xvidcore_warning)
+AC_MSG_RESULT($frontends_warning $audio_warning $videoapi_warning $moved_old_ffmpeg_warn $moved_old_libmpeg3_warn $vid_ffmpeg_warning $yasm_warn $vid_mpeg3_warning $vid_quicktime_warning $vid_xvidcore_warning)
diff --git a/docs/reference/txt/gap_gimprc_params.txt b/docs/reference/txt/gap_gimprc_params.txt
index 5eaebb4..41d0e62 100644
--- a/docs/reference/txt/gap_gimprc_params.txt
+++ b/docs/reference/txt/gap_gimprc_params.txt
@@ -51,6 +51,14 @@ If you edit gimprc files by hand, you must do this before you startup GIMP.
# the cache size can be set in kilobytes (K) or megaytes (M)
(video_playback_cache "100M")
+# the gap player supports caching of gimp tiles
+# note that frame playback does NOT use gimp_tiles (see video_playback_cache)
+# but caching of gimp tiles is relevant for other tile based processing features
+# (especially detail tracking that accesses the same region of gimp tiles in many loop iterations)
+# the cache size is specified in number of tiles.
+(video_player_cache_ntiles 200)
+
+
# the gap player has several widgets to set the
# position (e.g. the currently displayed framenumber)
#
@@ -359,3 +367,22 @@ If you edit gimprc files by hand, you must do this before you startup GIMP.
# number of video frames)
# default value is 36
(gap_ffetch_gva_frames_to_keep_cached "36")
+
+# gimp-gap has implemented 2 algorithms to locate small detail
+# of a layer in a corresponding layer.
+# the older implementation is slower but may may be activated
+# with this parameter for test purpose
+# default is no
+(gap-locate-details-use-old-algorithm "no")
+
+
+# Move Path debug feature to trigger logging current parameters
+# while rendering a frame to stdout.
+# default is no
+(video-move-path-log-render-params "no")
+
+# Move Path rotation threshold is a float number between 0.0 and 1.0
+# This threshold vaule allows Move path render engine to skip the time consuming
+# rotate transformations on very small angles.
+# default is 0.0125 degree
+(video-move-path-rotate-threshold "0.0125")
diff --git a/extern_libs/README_extern_libs b/extern_libs/README_extern_libs
index a590660..a7be7be 100755
--- a/extern_libs/README_extern_libs
+++ b/extern_libs/README_extern_libs
@@ -3,7 +3,7 @@ as sourcecode for convenient static linking.
CURRENT FFMPEG version is:
-- ffmpeg 0.6.1
+- ffmpeg 0.7.11
CURRENT LIBMPEG3 version is:
@@ -45,9 +45,16 @@ this GIMP-GAP distribution.
will work with a new ffmpeg version.
Typically it requires much testing and some knowledge
of both GIMP-GAP and ffmpeg internals to get new ffmpeg versions
- working.
+ working.
+
GIMP-GAP currently supports
+ o) ffmpeg-0.7.11 basically works, tests done
+ minor incopatibility detected at frame exact positioning compared against ffmpeg-0.6.1
+ on dvd mpeg2 encoded videos that do not start with a keyframe.
+ 0.7.11 delivers one extra gray frame as 1st and a black frame as 2nd frame
+ where 0.6.1 did deliver just one black frame as 1st frame.
+ o) ffmpeg-0.7.8 Not fully tested....
o) ffmpeg-0.6.1 tests repeated, same results as ffmpeg-0.6
o) ffmpeg-0.6 basically works, tests done
incopatibility detected at frame exact positioning compared against ffmpeg-0.5
@@ -60,11 +67,9 @@ this GIMP-GAP distribution.
when compiling/linking with ffmpeg-0.5)
-
- newer ffmpeg GIT (or SVN) snapshots
- may or may not compile, link and run with this GIMP-GAP release.
- (a compile/link test was done with ffmpeg-2010-09-14 snapshot
- that has no more support for libfaad --disable-ff-libfaad)
+ the ffmpeg releases 0.8.x, 0.9.x and latest GIT repository
+ are NOT compatible due to new API versions and will NOT compile with
+ this GIMP-GAP version.
GIMP-GAP can be configured to be compiled without ffmpeg (in this case
diff --git a/extern_libs/configure_options_ffmpeg.txt b/extern_libs/configure_options_ffmpeg.txt
index 6fa8162..d558146 100644
--- a/extern_libs/configure_options_ffmpeg.txt
+++ b/extern_libs/configure_options_ffmpeg.txt
@@ -1,4 +1,4 @@
---disable-shared --enable-static --enable-gpl --enable-mmx --enable-mmx2 --disable-vaapi
+ --disable-ffmpeg --disable-ffplay --disable-ffprobe --disable-ffserver --disable-shared --enable-static --enable-gpl --enable-mmx --enable-mmx2 --disable-vaapi
#
# This file provides configuration options for ffmpeg
# and is included while GIMP-GAP configure script runs the ffmpeg configuration.
diff --git a/extern_libs/configure_options_ffmpeg_win32.txt b/extern_libs/configure_options_ffmpeg_win32.txt
index 1287434..96a69b6 100644
--- a/extern_libs/configure_options_ffmpeg_win32.txt
+++ b/extern_libs/configure_options_ffmpeg_win32.txt
@@ -1,4 +1,4 @@
---disable-shared --enable-static --enable-gpl --enable-mmx --enable-mmx2 --disable-vaapi --disable-devices --disable-pthreads --disable-altivec --disable-sse --disable-neon
+ --disable-ffmpeg --disable-ffplay --disable-ffprobe --disable-ffserver --disable-shared --enable-static --enable-gpl --enable-mmx --enable-mmx2 --disable-vaapi --disable-devices --disable-pthreads --disable-altivec --disable-sse --disable-neon
#
# This file provides configuration options for ffmpeg
# and is included while GIMP-GAP configure scriptruns the ffmpeg configuration on Windows environment.
diff --git a/extern_libs/ffmpeg.tar.gz b/extern_libs/ffmpeg.tar.gz
index a7d7914..7a3cb42 100644
Binary files a/extern_libs/ffmpeg.tar.gz and b/extern_libs/ffmpeg.tar.gz differ
diff --git a/gap/Makefile.am b/gap/Makefile.am
index acb99ee..1173ccc 100644
--- a/gap/Makefile.am
+++ b/gap/Makefile.am
@@ -59,8 +59,12 @@ BASE_SOURCES = \
gap_lib.c \
gap_lib.h \
gap_lib_common_defs.h \
+ gap_detail_tracking_exec.c \
+ gap_detail_tracking_exec.h \
gap_locate.c \
gap_locate.h \
+ gap_locate2.c \
+ gap_locate2.h \
gap_lock.c \
gap_lock.h \
gap_navi_activtable.c \
@@ -69,6 +73,8 @@ BASE_SOURCES = \
gap_match.h \
gap_onion_base.c \
gap_onion_base.h \
+ gap_pixelrgn.c \
+ gap_pixelrgn.h \
gap_pdb_calls.c \
gap_pdb_calls.h \
gap_pview_da.c \
@@ -120,8 +126,10 @@ libgapstory_a_SOURCES = $(BASE_SOURCES) $(MOVEPATH_SOURCES) \
gap_story_syntax.c
libexec_PROGRAMS = \
+ gap_blend_fill \
gap_bluebox \
gap_colormask \
+ gap_detail_tracking \
gap_plugins \
gap_movepath \
gap_filter \
@@ -148,6 +156,13 @@ libexec_PROGRAMS = \
gap_wr_resynth \
gap_wr_opacity
+
+gap_blend_fill_SOURCES = \
+ gap_lastvaldesc.c \
+ gap_lastvaldesc.h \
+ gap_blend_fill_main.c \
+ gap_libgimpgap.h
+
gap_bluebox_SOURCES = \
gap_lastvaldesc.c \
gap_lastvaldesc.h \
@@ -164,6 +179,15 @@ gap_colormask_SOURCES = \
gap_colormask_main.c \
gap_libgimpgap.h
+
+gap_detail_tracking_SOURCES = \
+ gap_lastvaldesc.c \
+ gap_lastvaldesc.h \
+ gap_detail_tracking_main.c \
+ gap_detail_align_exec.c \
+ gap_detail_align_exec.h \
+ gap_libgimpgap.h
+
gap_plugins_SOURCES = \
gap_base_ops.c \
gap_base_ops.h \
@@ -415,6 +439,7 @@ gap_water_pattern_SOURCES = \
gap_water_pattern.c \
gap_libgimpgap.h
+
gap_wr_opacity_SOURCES = \
gap_lastvaldesc.c \
gap_lastvaldesc.h \
@@ -475,7 +500,9 @@ LDADD = $(GIMP_LIBS)
gap_plugins_LDADD = $(LIBGIMPGAP) $(LIBGAPBASE) $(GIMP_LIBS) -lm
gap_movepath_LDADD = $(LIBGIMPGAP) $(LIBGAPBASE) $(GIMP_LIBS)
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_filter_LDADD = $(GAPVIDEOAPI) $(LIBGIMPGAP) $(LIBGAPBASE) $(GIMP_LIBS)
gap_fmac_LDADD = $(GAPVIDEOAPI) $(LIBGIMPGAP) $(LIBGAPBASE) $(GIMP_LIBS)
gap_fmac_varying_LDADD = $(GAPVIDEOAPI) $(LIBGIMPGAP) $(LIBGAPBASE) $(GIMP_LIBS)
@@ -500,6 +527,7 @@ 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)
+
EXTRA_DIST = \
README \
README_developers \
diff --git a/gap/gap_blend_fill_main.c b/gap/gap_blend_fill_main.c
new file mode 100644
index 0000000..c7b83d4
--- /dev/null
+++ b/gap/gap_blend_fill_main.c
@@ -0,0 +1,1988 @@
+/* gap_blend_fill_main.c
+ * This filter fills the selected area via blending opposite border colors
+ * outside the selction into the selected area.
+ * It was implemented for fixing small pixel defects of my video camera sensor
+ * and is intended to be used as filter when processing video frames
+ * that are shot by such faulty cameras and typically runs
+ * as filtermacro. Therefore the selection can be provided via an external image
+ * or as path vectors via an extrernal SVG file.
+ *
+ * 2011/11/22
+ */
+/* The GIMP -- an image manipulation program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* Revision history
+ * (2011/11/22) 2.7.0 hof: created
+ */
+int gap_debug = 0; /* 1 == print debug infos , 0 dont print debug infos */
+#define GAP_DEBUG_DECLARED 1
+
+#define PLUG_IN_NAME "gap-blend-fill"
+#define PLUG_IN_BINARY "gap_blend_fill"
+#define PLUG_IN_PRINT_NAME "Blend Fill"
+#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-plug-in-blend-fill"
+
+
+#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 "gap_libgapbase.h"
+#include "gimplastvaldesc.h"
+#include "gap_image.h"
+#include "gap_arr_dialog.h"
+
+#include "gap-intl.h"
+
+#define SELECTION_FROM_VECTORS -2
+#define SELECTION_FROM_SVG_FILE -3
+
+#define ALPHA_NOT_SELECTED 0 /* marks pixel outside the selection */
+#define ALPHA_SELECTED_COLOR_UNDEFINED 128 /* marks pixel inside selection, color not (yet) calculated */
+#define ALPHA_SELECTED_COLOR_VALID 255 /* marks pixel inside selection, color already calculated */
+
+#define RED_IDX 0 /* index of red channel in RGBA model */
+#define BLU_IDX 1 /* index of green channel in RGBA model */
+#define GRN_IDX 2 /* index of blue channel in RGBA model */
+#define ALPHA_IDX 3 /* index of aplha channel in RGBA model */
+
+#define SCALE_MIN_WIDTH 300
+#define SPINBUTTON_MIN_WIDTH 50
+#define BUTTON_MIN_WIDTH 50
+#define MAX_SVG_SIZE 1600
+
+typedef struct FilterVals {
+ gboolean horizontalBlendFlag;
+ gboolean verticalBlendFlag;
+ gint32 altSelection;
+ gint32 borderRadius;
+ gchar selectionSVGFileName[MAX_SVG_SIZE]; /* contains small xml string or reference to SVG file */
+
+} FilterVals;
+
+
+
+typedef struct FilterContext {
+ gint32 imageId;
+ gint32 drawableId;
+ gint32 workLayerId;
+ gint workLayerWidth;
+ gint workLayerHeight;
+ gint workLayerOffsX;
+ gint workLayerOffsY;
+
+ gint origIntersectX;
+ gint origIntersectY;
+ FilterVals *valPtr;
+
+ gboolean doProgress;
+ gboolean doFlush;
+ gboolean doClearSelection;
+ gdouble progressStepsDone;
+ gdouble progressStepsTotal;
+
+} FilterContext;
+
+
+
+
+typedef struct BorderPixel {
+ gint pos;
+ guchar *colorRGBAPtr; /* NULL marks invalid color */
+} BorderPixel;
+
+typedef struct GuiStuff {
+ gint32 imageId;
+ GtkWidget *ok_button;
+ GtkWidget *msg_label;
+ GtkWidget *svg_entry;
+ GtkWidget *svg_filesel;
+ FilterVals *valPtr;
+} GuiStuff;
+
+
+static FilterVals fiVals;
+
+
+
+
+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 gint32 gap_blend_fill_apply_run(gint32 image_id, gint32 activeDrawableId, gboolean doProgress, gboolean doFlush, FilterVals *fiVals);
+static gboolean gap_blend_fill_dialog(FilterVals *fiVals, gint32 drawable_id);
+
+
+/* Global Variables */
+GimpPlugInInfo PLUG_IN_INFO =
+{
+ NULL, /* init_proc */
+ NULL, /* quit_proc */
+ query, /* query_proc */
+ run /* run_proc */
+};
+
+static const GimpParamDef in_args[] =
+{
+ { GIMP_PDB_INT32, "run-mode", "Interactive, non-interactive" },
+ { GIMP_PDB_IMAGE, "image", "Input image" },
+ { GIMP_PDB_DRAWABLE, "drawable", "Input layer (RGB or RGBA)" },
+ { GIMP_PDB_INT32, "horizontal", "0 .. dont blend colors horizontal. "
+ "1 .. blend colors horizontal." },
+ { GIMP_PDB_INT32, "vertical", "0 .. dont blend colors vertical. "
+ "1 .. blend colors vertical." },
+ { GIMP_PDB_DRAWABLE, "altSelection", "-1 use current selection, "
+ "-2 set selection by loading vectors from XML string (SVG XML string has to be provided in parameter selSVGFile) "
+ "-3 set selection by loading vectors from SVG file (name has to be provided in parameter selSVGFile) "
+ "or provide a valid positive drawable id of another layer (in another image) to copy selection from."
+ "(note: if the image where altSelection layer is part of has no selection, "
+ " then use a greayscale copy of the altSelection drawable as selection."
+ " and scale to current image size if necessary." },
+ { GIMP_PDB_INT32, "borderRadius", "radius for picking border colors (1 to 10)" },
+ { GIMP_PDB_STRING, "selSVGFile", "optional name of a file that contains the selection as vectors in SVG format. (set altSelection to -2)" }
+};
+
+
+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_GBOOLEAN (GIMP_ITER_FALSE, fiVals.horizontalBlendFlag, "horizontalBlendFlag"),
+ GIMP_LASTVALDEF_GBOOLEAN (GIMP_ITER_FALSE, fiVals.verticalBlendFlag, "verticalBlendFlag"),
+ GIMP_LASTVALDEF_DRAWABLE (GIMP_ITER_TRUE, fiVals.altSelection, "altSelection"),
+ GIMP_LASTVALDEF_GINT32 (GIMP_ITER_TRUE, fiVals.borderRadius, "borderRadius"),
+ GIMP_LASTVALDEF_ARRAY (GIMP_ITER_FALSE, fiVals.selectionSVGFileName, "svgFileNameArray"),
+ GIMP_LASTVALDEF_GCHAR (GIMP_ITER_FALSE, fiVals.selectionSVGFileName[0], "svgFilenameNameChar"),
+
+ };
+
+ /* registration for last values buffer structure (useful for animated filter apply) */
+ gimp_lastval_desc_register(PLUG_IN_NAME,
+ &fiVals,
+ sizeof(fiVals),
+ G_N_ELEMENTS (lastvals),
+ lastvals);
+
+
+ gimp_plugin_domain_register (GETTEXT_PACKAGE, LOCALEDIR);
+
+
+ /* the actual installation of the plugin */
+ gimp_install_procedure (PLUG_IN_NAME,
+ "Fill the selected area via blending border colors outside the selection.",
+ "Fill selected area by blending surrounding colors to cover it. "
+ "The fill area can be represented by the current selection "
+ "or by an alternative selction (provided as parameter altSelection) "
+ "If the image, that is refered by the altSelection drawable_id has a selection "
+ "then the refered selection is used to identify the fill area. "
+ "otherwise a grayscale copy of the altSelection drawable_id will be used "
+ "to identify the area that shall be filled. "
+ "altSelection value -1 indicates that the current selection of the input image shall be used. "
+ "This plug-in renders colors for all pixels in the selected area "
+ "This is done by blending colors of opposite border pixels outside the selection "
+ "and replaces colors of the selected area by the calculated blend color values. "
+ "The current implementation supports horizontal and/or vertical blend directions. "
+ "This filter is intended to fix small areas in videos shot with a camera that has some defect sensor pixels. "
+ " ",
+ PLUG_IN_AUTHOR,
+ PLUG_IN_COPYRIGHT,
+ GAP_VERSION_WITH_DATE,
+ N_("Blend Fill..."),
+ PLUG_IN_IMAGE_TYPES,
+ GIMP_PLUGIN,
+ global_number_in_args,
+ global_number_out_args,
+ in_args,
+ return_vals);
+
+
+ {
+ /* Menu names */
+ const char *menupath_image_layer_tranparency = N_("<Image>/Video/Layer/Enhance/");
+
+ gimp_plugin_menu_register (PLUG_IN_NAME, menupath_image_layer_tranparency);
+ }
+
+} /* end query */
+
+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 activeDrawableId = -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 */
+ fiVals.horizontalBlendFlag = TRUE;
+ fiVals.verticalBlendFlag = TRUE;
+ fiVals.altSelection = SELECTION_FROM_SVG_FILE;
+ fiVals.borderRadius = 3;
+ fiVals.selectionSVGFileName[0] = '\0';
+ g_snprintf(fiVals.selectionSVGFileName
+ , sizeof(fiVals.selectionSVGFileName), "%s"
+ , _("selection.svg"));
+
+ /* get image and drawable */
+ image_id = param[1].data.d_int32;
+ activeDrawableId = param[2].data.d_drawable;
+
+ /* Possibly retrieve data from a previous run */
+ gimp_get_data (name, &fiVals);
+
+ /* how are we running today? */
+ switch (run_mode)
+ {
+ gboolean dialogOk;
+
+ dialogOk = FALSE;
+
+ case GIMP_RUN_INTERACTIVE:
+ /* Get information from the dialog */
+ dialogOk = gap_blend_fill_dialog(&fiVals, activeDrawableId);
+ if (!dialogOk)
+ {
+ /* return without processing in case the Dialog was cancelled */
+ return;
+ }
+ 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.horizontalBlendFlag = (param[3].data.d_int32 == 0) ? FALSE : TRUE;
+ fiVals.verticalBlendFlag = (param[4].data.d_int32 == 0) ? FALSE : TRUE;
+ fiVals.altSelection = (gint32) param[5].data.d_drawable;
+ fiVals.borderRadius = CLAMP(param[6].data.d_int32, 1, 10);
+
+ fiVals.selectionSVGFileName[0] = '\0';
+ if(param[7].data.d_string != NULL)
+ {
+ g_snprintf(fiVals.selectionSVGFileName, MAX_SVG_SIZE -1, "%s", param[7].data.d_string);
+ }
+
+ }
+ 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)
+ {
+ /* Run the main function */
+ values[1].data.d_drawable =
+ gap_blend_fill_apply_run(image_id, activeDrawableId, doProgress, doFlush, &fiVals);
+
+ /* Store variable states for next run */
+ if (run_mode == GIMP_RUN_INTERACTIVE)
+ {
+ gimp_set_data (name, &fiVals, sizeof (FilterVals));
+ }
+
+ 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 */
+
+
+
+/* ---------------------------------------------- */
+/* ---------------------------------------------- */
+/* ---------------------------------------------- */
+/* ---------------------------------------------- */
+
+/* --------------------------
+ * p_to_double
+ * --------------------------
+ */
+static inline gdouble
+p_to_double(guchar val)
+{
+ gdouble dblValue;
+
+ dblValue = val;
+ return (dblValue);
+}
+
+
+/* --------------------------
+ * p_do_progress_steps
+ * --------------------------
+ */
+static void
+p_do_progress_steps(FilterContext *context, gint steps)
+{
+ if(context->doProgress)
+ {
+ gdouble currentProgress;
+
+ context->progressStepsDone += steps;
+ currentProgress = CLAMP(context->progressStepsDone / context->progressStepsTotal, 0.0, 1.0);
+ gimp_progress_update (currentProgress);
+ }
+
+} /* end p_do_progress_steps */
+
+
+/* --------------------------
+ * p_progress_init
+ * --------------------------
+ */
+static void
+p_progress_init(FilterContext *context)
+{
+ if(context->doProgress)
+ {
+ gdouble areaPixels;
+
+ areaPixels = context->workLayerWidth * context->workLayerHeight;
+ context->progressStepsDone = 0.0;
+
+ /* progress steps e.g. pixels to be handled for creating the work layer */
+ context->progressStepsTotal = areaPixels;
+
+ if (context->valPtr->horizontalBlendFlag)
+ {
+ context->progressStepsTotal += areaPixels;
+ }
+ if (context->valPtr->verticalBlendFlag)
+ {
+ context->progressStepsTotal += areaPixels;
+ }
+
+ gimp_progress_init( _("Blendfill ..."));
+
+ }
+
+} /* end p_progress_init */
+
+
+/* -------------------------------------
+ * p_mergeValidColors
+ * -------------------------------------
+ * merge colorRGBA into resultRGBA
+ * both are expected as guchar array with 4 elements for the red,green, blue and
+ * selection/processing status information in the alpha channel.
+ * - in case resultRGBA is still undefined
+ * the specified colorRGBA is copied to resultRGBA without merge.
+ * - in case both colors are undefined nothing is done.
+ */
+static void
+p_mergeValidColors(guchar *resultRGBAPtr, guchar *colorRGBAPtr)
+{
+ if ((colorRGBAPtr == NULL) || (resultRGBAPtr == NULL))
+ {
+ return;
+ }
+ if (resultRGBAPtr[ALPHA_IDX] != ALPHA_SELECTED_COLOR_VALID)
+ {
+ /* resultRGBAPtr is invalid, simply copy colorRGBAPtr because mix would give unwanted results */
+ resultRGBAPtr[RED_IDX] = colorRGBAPtr[RED_IDX];
+ resultRGBAPtr[BLU_IDX] = colorRGBAPtr[BLU_IDX];
+ resultRGBAPtr[GRN_IDX] = colorRGBAPtr[GRN_IDX];
+ resultRGBAPtr[ALPHA_IDX] = colorRGBAPtr[ALPHA_IDX];
+ }
+ else
+ {
+ gdouble r, g, b;
+
+ /* both resultRGBAPtr and colorRGBAPtr are valid, therefore merge colorRGBAPtr into result */
+ r = GAP_BASE_MIX_VALUE(0.5, p_to_double(resultRGBAPtr[0]), p_to_double(colorRGBAPtr[0]));
+ g = GAP_BASE_MIX_VALUE(0.5, p_to_double(resultRGBAPtr[1]), p_to_double(colorRGBAPtr[1]));
+ b = GAP_BASE_MIX_VALUE(0.5, p_to_double(resultRGBAPtr[2]), p_to_double(colorRGBAPtr[2]));
+
+ resultRGBAPtr[RED_IDX] = CLAMP(r, 0, 255);
+ resultRGBAPtr[BLU_IDX] = CLAMP(g, 0, 255);
+ resultRGBAPtr[GRN_IDX] = CLAMP(b, 0, 255);
+ resultRGBAPtr[ALPHA_IDX] = ALPHA_SELECTED_COLOR_VALID;
+ }
+
+} /* end p_mergeValidColors */
+
+
+/* -------------------------------------
+ * p_mixColorsWeightenedByDistance
+ * -------------------------------------
+ * mix both border colors weightened by position within selection length
+ */
+static inline void
+p_mixColorsWeightenedByDistance(guchar *colorRGBAPtr
+ , BorderPixel *border1, BorderPixel *border2, gint pos)
+{
+ gdouble r, g, b;
+ gdouble mixfactor;
+ gdouble selLength;
+ gdouble deltaPos;
+
+ selLength = 1 + (border2->pos - border1->pos);
+ deltaPos = pos - border1->pos;
+
+ mixfactor = CLAMP(deltaPos / selLength, 0.0, 1.0);
+
+ r = GAP_BASE_MIX_VALUE(mixfactor, p_to_double(border1->colorRGBAPtr[0]), p_to_double(border2->colorRGBAPtr[0]));
+ g = GAP_BASE_MIX_VALUE(mixfactor, p_to_double(border1->colorRGBAPtr[1]), p_to_double(border2->colorRGBAPtr[1]));
+ b = GAP_BASE_MIX_VALUE(mixfactor, p_to_double(border1->colorRGBAPtr[2]), p_to_double(border2->colorRGBAPtr[2]));
+
+ colorRGBAPtr[RED_IDX] = CLAMP(r, 0, 255);
+ colorRGBAPtr[BLU_IDX] = CLAMP(g, 0, 255);
+ colorRGBAPtr[GRN_IDX] = CLAMP(b, 0, 255);
+ colorRGBAPtr[ALPHA_IDX] = ALPHA_SELECTED_COLOR_VALID;
+
+} /* end p_mixColorsWeightenedByDistance */
+
+
+/* -------------------------------------
+ * p_copyBorderColor
+ * -------------------------------------
+ */
+static inline void
+p_copyBorderColor(guchar *colorRGBA, BorderPixel *border)
+{
+ colorRGBA[RED_IDX] = border->colorRGBAPtr[RED_IDX];
+ colorRGBA[BLU_IDX] = border->colorRGBAPtr[BLU_IDX];
+ colorRGBA[GRN_IDX] = border->colorRGBAPtr[GRN_IDX];
+ colorRGBA[ALPHA_IDX] = ALPHA_SELECTED_COLOR_VALID;
+
+} /* end p_copyBorderColor */
+
+
+
+/* -------------------------------------
+ * p_color_processing_one_pixel
+ * -------------------------------------
+ */
+static void
+p_color_processing_one_pixel(guchar *resultRGBAPtr, BorderPixel *border1, BorderPixel *border2, gint pos)
+{
+ guchar colorRGBA[4];
+ guchar *colorRGBAPtr;
+
+ if (resultRGBAPtr == NULL)
+ {
+ return;
+ }
+
+ colorRGBAPtr = NULL;
+
+ if (border1->colorRGBAPtr)
+ {
+ colorRGBAPtr = &colorRGBA[0];
+ if (border2->colorRGBAPtr)
+ {
+ p_mixColorsWeightenedByDistance(colorRGBAPtr, border1, border2, pos);
+ }
+ else
+ {
+ p_copyBorderColor(colorRGBAPtr, border1);
+ }
+ }
+ else if (border2->colorRGBAPtr)
+ {
+ colorRGBAPtr = &colorRGBA[0];
+ p_copyBorderColor(colorRGBAPtr, border2);
+ }
+ p_mergeValidColors(resultRGBAPtr, colorRGBAPtr);
+
+} /* end p_color_processing_one_pixel */
+
+
+/* ----------------------------------------
+ * p_mix_border_colors
+ * ----------------------------------------
+ * mix unselected neighbour border colors up to specified borderRadius
+ * in case there is only one border pixel available, the border->colorRGBAPtr
+ * is kept unchanged an no mix can be done.
+ * if there are 2 or more unselected neighbour border pixels available
+ * the border colors are mixed into mixRGBAPtr and this resulting mixed color
+ * is set as new border->colorRGBAPtr.
+ * The mix is done 50:50 in a loop, therefore the nearest border pixel is processed
+ * as the last one and has typically more weight than the farest border pixel.
+ */
+static void
+p_mix_border_colors(guchar *mixRGBAPtr, guchar *pixLine, gint lineLength
+ , BorderPixel *border, FilterContext *context, gint step)
+{
+ gint startPos;
+ gint pos;
+ gint idx;
+ gint radius;
+
+ if (border->colorRGBAPtr == NULL)
+ {
+ return;
+ }
+
+ radius = 1;
+ startPos = pos;
+ for (pos = border->pos + step; ((pos < lineLength) && (pos >= 0)); pos += step)
+ {
+ idx = pos * 4;
+ if (pixLine[idx + ALPHA_IDX] != ALPHA_NOT_SELECTED)
+ {
+ break;
+ }
+ radius++;
+ startPos = pos;
+ if(radius >= context->valPtr->borderRadius)
+ {
+ break;
+ }
+ }
+
+ if (radius > 1)
+ {
+ gint cnt;
+
+ pos = startPos;
+ idx = pos * 4;
+
+ mixRGBAPtr[RED_IDX] = pixLine[idx + RED_IDX];
+ mixRGBAPtr[BLU_IDX] = pixLine[idx + BLU_IDX];
+ mixRGBAPtr[GRN_IDX] = pixLine[idx + GRN_IDX];
+ mixRGBAPtr[ALPHA_IDX] = ALPHA_SELECTED_COLOR_VALID;
+
+
+ for (cnt = 1; cnt < radius; cnt++)
+ {
+ pos -= step;
+ idx = pos * 4;
+
+ p_mergeValidColors(mixRGBAPtr, &pixLine[idx]);
+
+
+ }
+ border->colorRGBAPtr = mixRGBAPtr;
+
+ }
+
+} /* end p_mix_border_colors */
+
+
+/* ----------------------------------------
+ * p_color_blend_pixel_line
+ * ----------------------------------------
+ * pixLine input is expected as RGBA8 pixel row (or column)
+ * where the Alpha channel byte is used as selection and processing state information
+ * (and does not represent transparency as normal RGBA colormodel would)
+ * The algorithm iterates along the line and fills all selected pixels
+ * (identified by Alpha != ALPHA_NOT_SELECTED) with color blend from
+ * last border pixel color to next border pixel.
+ * The original color of the pixels is ignored in case the pixel has
+ * the state ALPHA_SELECTED_COLOR_UNDEFINED but the state is set
+ * to ALPHA_SELECTED_COLOR_VALID when the new color is assigned to the pixel.
+ *
+ * If only one border pixel was found, all its selected neighbour pixels
+ * ill be filled with the border color without blending.
+ * If there is NO unselected border pixel at all, the selected pixel keeps
+ * its original color.
+ *
+ * in case the processed SELECTED pixel has a already reached state ALPHA_SELECTED_COLOR_VALID
+ * it is mixed 50:50 with the calculated blend (or border) color value.
+ * (this will occure when both horizontal and vertical blend is applied)
+ *
+ */
+static void
+p_color_blend_pixel_line(guchar *pixLine, gint lineLength, FilterContext *context)
+{
+ gint pos;
+ gint idx;
+ BorderPixel border1;
+ BorderPixel border2;
+ gboolean insideSelecton;
+ guchar mix1RGBA[4];
+ guchar mix2RGBA[4];
+
+ border1.colorRGBAPtr = NULL;
+ border2.colorRGBAPtr = NULL;
+ insideSelecton = FALSE;
+ idx = 0;
+ for (pos = 0; pos < lineLength; pos++)
+ {
+ if (pixLine[idx + ALPHA_IDX] == ALPHA_NOT_SELECTED)
+ {
+ insideSelecton = FALSE;
+ border1.pos = pos;
+ border1.colorRGBAPtr = &pixLine[idx];
+ border2.colorRGBAPtr = NULL;
+ }
+ else
+ {
+ if (insideSelecton != TRUE)
+ {
+ gint pos2;
+ gint idx2;
+ insideSelecton = TRUE;
+
+ /* find (border2) the next pixel that is outside the selection */
+ idx2 = idx +4;
+ for (pos2 = pos +1; pos2 < lineLength; pos2++)
+ {
+ if (pixLine[idx2 + ALPHA_IDX] == ALPHA_NOT_SELECTED)
+ {
+ border2.pos = pos2;
+ border2.colorRGBAPtr = &pixLine[idx2];
+ break;
+ }
+ idx2 += 4;
+ }
+
+ if (context->valPtr->borderRadius > 1)
+ {
+ p_mix_border_colors(&mix1RGBA[0], pixLine, lineLength, &border1, context, -1);
+ p_mix_border_colors(&mix2RGBA[0], pixLine, lineLength, &border2, context, 1);
+ }
+
+ }
+ p_color_processing_one_pixel(&pixLine[idx], &border1, &border2, pos);
+ }
+
+ idx += 4;
+ }
+
+ p_do_progress_steps(context, lineLength);
+
+} /* end p_color_blend_pixel_line */
+
+
+/* ----------------------------------------
+ * p_horizontal_color_blend
+ * ----------------------------------------
+ */
+static void
+p_horizontal_color_blend(FilterContext *context)
+{
+ GimpPixelRgn workPR;
+ GimpDrawable *workDrawable;
+ guchar *pixLine;
+ gint row;
+
+ workDrawable = gimp_drawable_get (context->workLayerId);
+ gimp_pixel_rgn_init (&workPR, workDrawable
+ , 0, 0
+ , context->workLayerWidth, context->workLayerHeight
+ , TRUE /* dirty */
+ , FALSE /* shadow */
+ );
+
+
+
+ /* allocate buffer for one horizontal pixel line */
+ pixLine = g_new (guchar, context->workLayerWidth * workDrawable->bpp);
+
+ for (row = 0; row < context->workLayerHeight; row++)
+ {
+ gimp_pixel_rgn_get_row (&workPR, pixLine, 0, row, context->workLayerWidth);
+ p_color_blend_pixel_line(pixLine, context->workLayerWidth, context);
+ gimp_pixel_rgn_set_row (&workPR, pixLine, 0, row, context->workLayerWidth);
+
+ }
+
+ gimp_drawable_flush (workDrawable);
+ gimp_drawable_update (context->workLayerId, 0, 0, context->workLayerWidth, context->workLayerHeight);
+ gimp_drawable_detach(workDrawable);
+
+ g_free (pixLine);
+
+} /* end p_horizontal_color_blend */
+
+
+/* ----------------------------------------
+ * p_vertical_color_blend
+ * ----------------------------------------
+ */
+static void
+p_vertical_color_blend(FilterContext *context)
+{
+ GimpPixelRgn workPR;
+ GimpDrawable *workDrawable;
+ guchar *pixLine;
+ gint col;
+
+ workDrawable = gimp_drawable_get (context->workLayerId);
+ gimp_pixel_rgn_init (&workPR, workDrawable
+ , 0, 0
+ , context->workLayerWidth, context->workLayerHeight
+ , TRUE /* dirty */
+ , FALSE /* shadow */
+ );
+
+
+
+ /* allocate buffer for one vertical pixel line */
+ pixLine = g_new (guchar, context->workLayerHeight * workDrawable->bpp);
+
+ for (col = 0; col < context->workLayerWidth; col++)
+ {
+ gimp_pixel_rgn_get_col (&workPR, pixLine, col, 0, context->workLayerHeight);
+ p_color_blend_pixel_line(pixLine, context->workLayerHeight, context);
+ gimp_pixel_rgn_set_col (&workPR, pixLine, col, 0, context->workLayerHeight);
+
+ }
+
+ gimp_drawable_flush (workDrawable);
+ gimp_drawable_update (context->workLayerId, 0, 0, context->workLayerWidth, context->workLayerHeight);
+ gimp_drawable_detach(workDrawable);
+
+ g_free (pixLine);
+
+} /* end p_vertical_color_blend */
+
+
+
+
+
+
+/* --------------------------
+ * p_set_altSelection
+ * --------------------------
+ * create selection as Grayscale copy of the specified altSelection layer
+ * - operates on a duplicate of the image references by altSelection
+ * - this duplicate is scaled to same size as specified image_id
+ *
+ * - if altSelection refers to an image that has a selection
+ * then use this selection instead of the layer itself.
+ */
+static gboolean
+p_set_altSelection(FilterContext *context)
+{
+ gboolean l_rc;
+ if(gap_debug)
+ {
+ printf("p_set_altSelection: drawable_id:%d altSelection:%d\n"
+ ,(int)context->drawableId
+ ,(int)context->valPtr->altSelection
+ );
+ }
+
+ if ((context->drawableId == context->valPtr->altSelection) || (context->drawableId < 0))
+ {
+ return (FALSE);
+ }
+ context->doClearSelection = TRUE;
+ l_rc = gap_image_set_selection_from_selection_or_drawable(context->imageId
+ , context->valPtr->altSelection
+ , FALSE);
+
+ return (l_rc);
+}
+
+
+
+/* ---------------------------------
+ * p_rgn_render_init_workregion
+ * ---------------------------------
+ */
+static void
+p_rgn_render_init_workregion (const GimpPixelRgn *origPR
+ , const GimpPixelRgn *workPR
+ , const GimpPixelRgn *selPR
+ , FilterContext *context)
+{
+ guint row;
+ guchar* orig = origPR->data;
+ guchar* work = workPR->data;
+ guchar* sel = selPR->data;
+
+
+ if(gap_debug)
+ {
+ printf("p_rgn_render_init_workregion orig W:%d H:%d work W:%d H:%d sel W:%d H:%d\n"
+ ,(int)origPR->w
+ ,(int)origPR->h
+ ,(int)workPR->w
+ ,(int)workPR->h
+ ,(int)selPR->w
+ ,(int)selPR->h
+ );
+ }
+
+
+ for (row = 0; row < MIN(origPR->h, workPR->h); row++)
+ {
+ guint col;
+ guint origIdx = 0;
+ guint workIdx = 0;
+ guint selIdx = 0;
+
+ for (col = 0; col < MIN(origPR->w, workPR->w); col++)
+ {
+ work[workIdx] = orig[origIdx];
+ work[workIdx +1] = orig[origIdx +1];
+ work[workIdx +2] = orig[origIdx +2];
+
+ work[workIdx + ALPHA_IDX] = ALPHA_NOT_SELECTED;
+ if (col < selPR->w)
+ {
+ if (sel[selIdx] != 0)
+ {
+ work[workIdx + ALPHA_IDX] = ALPHA_SELECTED_COLOR_UNDEFINED;
+ }
+ }
+
+ origIdx += origPR->bpp;
+ workIdx += workPR->bpp;
+ selIdx += selPR->bpp;
+ }
+
+ orig += origPR->rowstride;
+ work += workPR->rowstride;
+ sel += selPR->rowstride;
+ }
+
+ p_do_progress_steps(context, workPR->h * workPR->w);
+
+} /* end p_rgn_render_init_workregion */
+
+
+/* ----------------------------------
+ * p_set_selection_from_vectors_string
+ * ----------------------------------
+ * interpret the selectionSVGFileName buffer as XML svg content
+ * and load all path vectors from this string.
+ * (on errors keep current selection)
+ */
+static void
+p_set_selection_from_vectors_string(FilterContext *context)
+{
+ gboolean vectorsOk;
+ gint num_vectors;
+ gint32 *vectors_ids;
+
+ vectorsOk = FALSE;
+ if (context->valPtr->selectionSVGFileName != '\0')
+ {
+ gint length;
+
+ length = strlen(context->valPtr->selectionSVGFileName);
+ vectorsOk = gimp_vectors_import_from_string (context->imageId
+ ,context->valPtr->selectionSVGFileName
+ ,length
+ , 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))
+ {
+ 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_remove_vectors(context->imageId, vectorId);
+ context->doClearSelection = TRUE;
+ }
+
+} /* end p_set_selection_from_vectors_string */
+
+
+
+/* ----------------------------------
+ * 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(FilterContext *context)
+{
+ gboolean vectorsOk;
+ gint num_vectors;
+ gint32 *vectors_ids;
+
+ vectorsOk = FALSE;
+ if (context->valPtr->selectionSVGFileName != '\0')
+ {
+ if(g_file_test(context->valPtr->selectionSVGFileName, G_FILE_TEST_EXISTS))
+ {
+ vectorsOk = gimp_vectors_import_from_file (context->imageId
+ ,context->valPtr->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))
+ {
+ 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_remove_vectors(context->imageId, vectorId);
+ context->doClearSelection = TRUE;
+ }
+
+} /* end p_set_selection_from_vectors_file */
+
+
+
+/* --------------------------
+ * p_render_initial_workLayer
+ * --------------------------
+ * Creates a worklayer in size of the interection of
+ * the current selection (expanded by borderRadius) and the input Layer (origDrawable).
+ * The worklayer has type RGBA, but the Alpha channel byte is not used for transparency.
+ * It represents selection and processing state information that is initalized with:
+ * ALPHA_NOT_SELECTED for all pixels outside the current (unexpanded) selection
+ * ALPHA_SELECTED_COLOR_UNDEFINED for all pixels outside the current (unexpanded) selection
+ * Note that this implicite sharpens the selection, e.g. weak selected
+ * pixels are considered as selected and will be processed too.
+ */
+static void
+p_render_initial_workLayer(FilterContext *context)
+{
+ GimpPixelRgn origPR, workPR, selPR;
+ GimpDrawable *origDrawable;
+ GimpDrawable *workDrawable;
+ GimpDrawable *selDrawable;
+ gpointer pr;
+
+ origDrawable = gimp_drawable_get (context->drawableId);
+ workDrawable = gimp_drawable_get (context->workLayerId);
+ selDrawable = gimp_drawable_get (gimp_image_get_selection(context->imageId));
+
+ gimp_pixel_rgn_init (&origPR, origDrawable
+ , context->origIntersectX, context->origIntersectY
+ , context->workLayerWidth, context->workLayerHeight
+ , FALSE /* dirty */
+ , FALSE /* shadow */
+ );
+
+ gimp_pixel_rgn_init (&workPR, workDrawable
+ , 0, 0
+ , context->workLayerWidth, context->workLayerHeight
+ , TRUE /* dirty */
+ , FALSE /* shadow */
+ );
+ gimp_pixel_rgn_init (&selPR, selDrawable
+ , context->workLayerOffsX, context->workLayerOffsY
+ , context->workLayerWidth, context->workLayerHeight
+ , FALSE /* dirty */
+ , FALSE /* shadow */
+ );
+
+ for (pr = gimp_pixel_rgns_register (3, &origPR, &workPR, &selPR);
+ pr != NULL;
+ pr = gimp_pixel_rgns_process (pr))
+ {
+ p_rgn_render_init_workregion (&origPR, &workPR, &selPR, context);
+ }
+
+ /* update the processed region */
+ gimp_drawable_flush (workDrawable);
+
+ gimp_drawable_update (context->workLayerId, 0, 0, context->workLayerWidth, context->workLayerHeight);
+
+ gimp_drawable_detach(origDrawable);
+ gimp_drawable_detach(workDrawable);
+ gimp_drawable_detach(selDrawable);
+
+} /* end p_render_initial_workLayer */
+
+
+
+
+
+
+/* ----------------------------------------
+ * p_create_workLayer
+ * ----------------------------------------
+ * create the workLayer 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
+ * are copied from the drawable.
+ */
+static void
+p_create_workLayer(FilterContext *context)
+{
+ gboolean has_selection;
+ gboolean non_empty;
+ gboolean altSelection_success;
+ gint x1, y1, x2, y2;
+ gint ix, iy, ix1, iy1, ix2, iy2;
+ gint iWidth, iHeight;
+ gint borderRadius;
+
+
+ altSelection_success = FALSE;
+ borderRadius = context->valPtr->borderRadius;
+
+ if(context->valPtr->altSelection >= 0)
+ {
+ if(gap_debug)
+ {
+ printf("creating altSelection: %d\n", (int)context->valPtr->altSelection);
+ }
+ altSelection_success = p_set_altSelection(context);
+ }
+
+ if(context->valPtr->altSelection == SELECTION_FROM_VECTORS)
+ {
+ p_set_selection_from_vectors_string(context);
+ }
+ else if(context->valPtr->altSelection == SELECTION_FROM_SVG_FILE)
+ {
+ p_set_selection_from_vectors_file(context);
+ }
+
+ has_selection = gimp_selection_bounds(context->imageId, &non_empty, &x1, &y1, &x2, &y2);
+
+ context->workLayerId = -1;
+ if (non_empty != TRUE)
+ {
+ return;
+ }
+
+ non_empty = gimp_drawable_mask_intersect(context->drawableId, &ix, &iy, &iWidth, &iHeight);
+ if((non_empty != TRUE)
+ || (iWidth <= 0)
+ || (iHeight <= 0))
+ {
+ return;
+ }
+
+ gimp_drawable_offsets(context->drawableId
+ ,&context->workLayerOffsX
+ ,&context->workLayerOffsY
+ );
+
+ ix2 = MIN(gimp_drawable_width(context->drawableId), ix + iWidth + borderRadius);
+ iy2 = MIN(gimp_drawable_height(context->drawableId), iy + iHeight + borderRadius);
+
+ ix1 = MAX(0, ix - borderRadius);
+ iy1 = MAX(0, iy - borderRadius);
+
+ context->workLayerOffsX += ix1;
+ context->workLayerOffsY += iy1;
+ context->workLayerWidth = ix2 - ix1;
+ context->workLayerHeight = iy2 - iy1;
+
+ context->origIntersectX = ix1;
+ context->origIntersectY = iy1;
+
+ if(gap_debug)
+ {
+ printf("intersect ix:%d iy:%d iWidth:%d iHeight:%d ix1:%d iy1:%d ix2:%d iy2:%d\n"
+ ,(int)ix
+ ,(int)iy
+ ,(int)iWidth
+ ,(int)iHeight
+ ,(int)ix1
+ ,(int)iy1
+ ,(int)ix2
+ ,(int)iy2
+ );
+ printf("workLayerOffsX:%d workLayerOffsY:%d workLayerWidth:%d workLayerHeight:%d\n"
+ ,(int)context->workLayerOffsX
+ ,(int)context->workLayerOffsY
+ ,(int)context->workLayerWidth
+ ,(int)context->workLayerHeight
+ );
+ }
+
+ p_progress_init(context);
+
+ context->workLayerId = gimp_layer_new(context->imageId
+ , "work"
+ , context->workLayerWidth
+ , context->workLayerHeight
+ , GIMP_RGBA_IMAGE
+ , 100.0 /* full opacity */
+ , 0 /* normal mode */
+ );
+ gimp_image_add_layer(context->imageId, context->workLayerId, 0);
+ gimp_layer_set_offsets(context->workLayerId
+ , context->workLayerOffsX
+ , context->workLayerOffsY
+ );
+
+ p_render_initial_workLayer(context);
+
+} /* end p_create_workLayer */
+
+
+
+
+
+/* ----------------------------------
+ * gap_blend_fill_apply_run
+ * ----------------------------------
+ */
+static gint32
+gap_blend_fill_apply_run(gint32 image_id, gint32 activeDrawableId, gboolean doProgress, gboolean doFlush, FilterVals *fiVals)
+{
+ FilterContext filterContext;
+ FilterContext *context;
+ gint32 rc;
+
+ gimp_image_undo_group_start (image_id);
+
+ /* init the context */
+ context = &filterContext;
+ context->doProgress = doProgress;
+ context->doFlush = doFlush;
+ context->doClearSelection = FALSE;
+ context->imageId = image_id;
+ context->drawableId = activeDrawableId;
+ context->workLayerId = -1;
+ context->valPtr = fiVals;
+ fiVals->borderRadius = CLAMP(fiVals->borderRadius, 1, 10);
+
+ rc = -1;
+
+ if(gap_debug)
+ {
+ printf("context Svg:%s\n", context->valPtr->selectionSVGFileName);
+ }
+
+ p_create_workLayer(context);
+ if (context->workLayerId >= 0)
+ {
+ if (context->valPtr->horizontalBlendFlag)
+ {
+ p_horizontal_color_blend(context);
+ }
+
+ if (context->valPtr->verticalBlendFlag)
+ {
+ p_vertical_color_blend(context);
+ }
+
+ rc = gimp_image_merge_down(image_id, context->workLayerId, GIMP_EXPAND_AS_NECESSARY);
+
+ if(context->doClearSelection)
+ {
+ gimp_selection_none(context->imageId);
+ }
+
+ }
+
+ gimp_image_undo_group_end (image_id);
+ return (rc);
+} /* end gap_blend_fill_apply_run */
+
+
+
+/* --------------------------- DIALOG stuff ------------------- */
+/* --------------------------- DIALOG stuff ------------------- */
+/* --------------------------- DIALOG stuff ------------------- */
+/* --------------------------- DIALOG stuff ------------------- */
+/* --------------------------- DIALOG stuff ------------------- */
+/* --------------------------- DIALOG stuff ------------------- */
+/* --------------------------- DIALOG stuff ------------------- */
+
+
+
+/* --------------------------------------
+ * on_gboolean_button_update
+ * --------------------------------------
+ */
+static void
+on_gboolean_button_update (GtkWidget *widget,
+ gpointer data)
+{
+ gint *toggle_val = (gint *) data;
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
+ {
+ *toggle_val = TRUE;
+ }
+ else
+ {
+ *toggle_val = FALSE;
+ }
+}
+
+
+/* ----------------------------
+ * p_selectionConstraintFunc
+ * ----------------------------
+ *
+ */
+static gint
+p_selectionConstraintFunc (gint32 image_id,
+ gint32 drawable_id,
+ gpointer data)
+{
+ if (image_id < 0)
+ {
+ return FALSE;
+ }
+
+ /* dont accept layers from indexed images */
+ if (gimp_drawable_is_indexed (drawable_id))
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+} /* end p_selectionConstraintFunc */
+
+
+/* --------------------------------------------
+ * p_check_exec_condition_and_set_ok_sesitivity
+ * --------------------------------------------
+ */
+static void
+p_check_exec_condition_and_set_ok_sesitivity(GuiStuff *guiStuffPtr)
+{
+ gboolean okButtonSensitive;
+
+ okButtonSensitive = TRUE;
+
+ if ((guiStuffPtr->valPtr == NULL)
+ || (guiStuffPtr->msg_label == NULL))
+ {
+ return;
+ }
+ gtk_label_set_text(GTK_LABEL(guiStuffPtr->msg_label), "");
+
+ if (guiStuffPtr->valPtr->altSelection == SELECTION_FROM_VECTORS)
+ {
+ gchar *svgString;
+ gint *allVectors;
+ gint num_vectors;
+
+ svgString = NULL;
+ allVectors = gimp_image_get_vectors(guiStuffPtr->imageId, &num_vectors);
+ if(allVectors != NULL)
+ {
+ g_free(allVectors);
+ }
+ if(num_vectors > 0)
+ {
+ svgString = gimp_vectors_export_to_string (guiStuffPtr->imageId
+ , 0 /* vectors_ID for all vectors merged */
+ );
+ }
+
+ if (svgString != NULL)
+ {
+ gint length;
+
+ length = strlen(svgString);
+ if(length >= sizeof(guiStuffPtr->valPtr->selectionSVGFileName))
+ {
+ gchar *msg;
+
+ msg = g_strdup_printf(_("Path Vectors too large to fit into buffersize:%d.")
+ , sizeof(guiStuffPtr->valPtr->selectionSVGFileName));
+ gtk_label_set_text(GTK_LABEL(guiStuffPtr->msg_label), msg);
+ g_free(msg);
+ okButtonSensitive = FALSE;
+ }
+ g_free(svgString);
+ }
+ else
+ {
+ gtk_label_set_text(GTK_LABEL(guiStuffPtr->msg_label)
+ , _("No Path Vectors available."));
+ okButtonSensitive = FALSE;
+ }
+ }
+
+ if (guiStuffPtr->valPtr->altSelection == SELECTION_FROM_SVG_FILE)
+ {
+ okButtonSensitive = FALSE;
+ if (guiStuffPtr->valPtr->selectionSVGFileName[0] != '\0')
+ {
+ if(g_file_test(guiStuffPtr->valPtr->selectionSVGFileName, G_FILE_TEST_EXISTS))
+ {
+ okButtonSensitive = TRUE;
+ }
+ else
+ {
+ gtk_label_set_text(GTK_LABEL(guiStuffPtr->msg_label)
+ , _("SVG file does not exist (use Save Pats button to create)."));
+ }
+ }
+ else
+ {
+ gtk_label_set_text(GTK_LABEL(guiStuffPtr->msg_label)
+ , _("please enter SVG filename"));
+ }
+ }
+
+ if(guiStuffPtr->ok_button)
+ {
+ gtk_widget_set_sensitive(guiStuffPtr->ok_button, okButtonSensitive);
+
+ }
+
+} /* end p_check_exec_condition_and_set_ok_sesitivity */
+
+/* ----------------------------
+ * p_selectionComboCallback
+ * ----------------------------
+ *
+ */
+static void
+p_selectionComboCallback (GtkWidget *widget, gint32 *layerId)
+{
+ gint value;
+
+ gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), &value);
+
+ if(gap_debug)
+ {
+ printf("p_selectionComboCallback: LayerAddr:%d value:%d\n"
+ ,(int)layerId
+ ,(int)value
+ );
+ }
+
+ if(layerId != NULL)
+ {
+ GuiStuff *guiStuffPtr;
+
+ *layerId = value;
+
+ guiStuffPtr = (GuiStuff *) g_object_get_data (G_OBJECT (widget), "guiStuffPtr");
+ if(guiStuffPtr != NULL)
+ {
+ p_check_exec_condition_and_set_ok_sesitivity(guiStuffPtr);
+ }
+ }
+
+} /* end p_selectionComboCallback */
+
+
+
+
+
+/* ---------------------------------
+ * 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 */
+
+
+
+/* ---------------------------------
+ * on_svg_entry_changed
+ * ---------------------------------
+ */
+static void
+on_svg_entry_changed (GtkEditable *editable,
+ GuiStuff *guiStuffPtr)
+{
+ GtkEntry *entry;
+
+ if(gap_debug)
+ {
+ printf("CB: on_svg_entry_changed\n");
+ }
+ if(guiStuffPtr == NULL)
+ {
+ return;
+ }
+
+ entry = GTK_ENTRY(guiStuffPtr->svg_entry);
+ if(entry)
+ {
+ g_snprintf(guiStuffPtr->valPtr->selectionSVGFileName
+ , sizeof(guiStuffPtr->valPtr->selectionSVGFileName), "%s"
+ , gtk_entry_get_text(entry));
+ 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 */
+
+
+/* ---------------------------------
+ * on_save_svg_clicked
+ * ---------------------------------
+ */
+static void
+on_save_svg_clicked(GtkButton *button,
+ GuiStuff *guiStuffPtr)
+{
+ if (guiStuffPtr->valPtr->selectionSVGFileName != '\0')
+ {
+ gboolean l_wr_permission;
+
+ l_wr_permission = gap_arr_overwrite_file_dialog(guiStuffPtr->valPtr->selectionSVGFileName);
+
+ if(l_wr_permission)
+ {
+ gboolean svgExportOk;
+ gint32 vectors_ID;
+
+ vectors_ID = 0; /* 0 refers to all vectors in the image */
+ svgExportOk = gimp_vectors_export_to_file(guiStuffPtr->imageId
+ , guiStuffPtr->valPtr->selectionSVGFileName
+ , vectors_ID
+ );
+ if (svgExportOk != TRUE)
+ {
+ g_message(_("Failed to write SVG file: %s")
+ , guiStuffPtr->valPtr->selectionSVGFileName);
+ }
+ }
+ }
+} /* end on_save_svg_clicked */
+
+
+
+
+/* ----------------------------------
+ * p_save_vectors_to_string
+ * ----------------------------------
+ */
+static void
+p_save_vectors_to_string(GuiStuff *guiStuffPtr)
+{
+ gchar *svgString;
+
+ svgString = gimp_vectors_export_to_string (guiStuffPtr->imageId, 0 /* vectors_ID for all vectors merged */);
+ if (svgString != NULL)
+ {
+ gint length;
+
+ if(gap_debug)
+ {
+ printf("svgString:%s\n", svgString);
+ }
+ length = strlen(svgString);
+ if(length < sizeof(guiStuffPtr->valPtr->selectionSVGFileName))
+ {
+ g_snprintf(guiStuffPtr->valPtr->selectionSVGFileName
+ , sizeof(guiStuffPtr->valPtr->selectionSVGFileName), "%s"
+ , svgString);
+
+ }
+ else
+ {
+ g_message(_("Path Vectors too large to fit into buffersize:%d.")
+ , sizeof(guiStuffPtr->valPtr->selectionSVGFileName));
+ }
+ g_free(svgString);
+ }
+ else
+ {
+ g_message(_("No Path Vectors available."));
+ }
+
+} /* end p_save_vectors_to_string */
+
+
+/* --------------------------
+ * gap_blend_fill_dialog
+ * --------------------------
+ */
+static gboolean
+gap_blend_fill_dialog (FilterVals *fiVals, gint32 drawable_id)
+{
+ GuiStuff guiStuffRecord;
+ GuiStuff *guiStuffPtr;
+ GtkWidget *dialog;
+ GtkWidget *main_vbox;
+ GtkWidget *label;
+ GtkWidget *button;
+ GtkWidget *table;
+ GtkWidget *combo;
+ GtkWidget *entry;
+ GtkWidget *checkbutton;
+ GtkObject *adj;
+ gint row;
+ gboolean run;
+ gint initalComboElem;
+
+
+ guiStuffPtr = &guiStuffRecord;
+ guiStuffPtr->imageId = gimp_drawable_get_image(drawable_id);
+ guiStuffPtr->msg_label = NULL;
+ guiStuffPtr->svg_entry = NULL;
+ guiStuffPtr->svg_filesel = NULL;
+ guiStuffPtr->valPtr = fiVals;
+
+
+ fiVals->altSelection = -1;
+
+ gimp_ui_init (PLUG_IN_BINARY, TRUE);
+
+ dialog = gimp_dialog_new (_("Blend Fill Selection"), PLUG_IN_BINARY,
+ NULL, 0,
+ gimp_standard_help_func, PLUG_IN_NAME,
+
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+
+ NULL);
+
+ button = gimp_dialog_add_button (GIMP_DIALOG(dialog), GTK_STOCK_OK, GTK_RESPONSE_OK);
+ guiStuffPtr->ok_button = button;
+
+ gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
+ GTK_RESPONSE_OK,
+ GTK_RESPONSE_CANCEL,
+ -1);
+
+ gimp_window_set_transient (GTK_WINDOW (dialog));
+
+ main_vbox = gtk_vbox_new (FALSE, 12);
+ gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12);
+ gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), main_vbox);
+ gtk_widget_show (main_vbox);
+
+
+ /* Controls */
+ table = gtk_table_new (3, 3, FALSE);
+ gtk_table_set_col_spacings (GTK_TABLE (table), 6);
+ gtk_table_set_row_spacings (GTK_TABLE (table), 6);
+ gtk_box_pack_start (GTK_BOX (main_vbox), table, FALSE, FALSE, 0);
+ gtk_widget_show (table);
+
+ row = 0;
+
+ /* horizontalBlendFlag checkbutton */
+ label = gtk_label_new (_("fills the selection by blending opposite border colors "
+ "outside the selction to cover the selected area.\n"
+ "Intended to fix small pixel errors"));
+ gtk_widget_show (label);
+ gtk_table_attach (GTK_TABLE (table), label, 0, 3, row, row+1,
+ (GtkAttachOptions) (GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+ gtk_misc_set_alignment (GTK_MISC (label), 0.5, 0.5);
+
+ row++;
+
+ /* horizontalBlendFlag checkbutton */
+ label = gtk_label_new (_("Horizontal Blend:"));
+ gtk_widget_show (label);
+ gtk_table_attach (GTK_TABLE (table), 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 (" ");
+ gimp_help_set_help_data (checkbutton, _("ON: enable horizontal color blending. "
+ "OFF: disable horizontal color blending."), NULL);
+ gtk_widget_show (checkbutton);
+ gtk_table_attach( GTK_TABLE(table), checkbutton, 1, 2, row, row+1,
+ GTK_FILL, 0, 0, 0 );
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (checkbutton), fiVals->horizontalBlendFlag);
+ g_signal_connect (checkbutton, "toggled",
+ G_CALLBACK (on_gboolean_button_update),
+ &fiVals->horizontalBlendFlag);
+
+ row++;
+
+ /* verticalBlendFlag checkbutton */
+ label = gtk_label_new (_("Vertical Blend:"));
+ gtk_widget_show (label);
+ gtk_table_attach (GTK_TABLE (table), 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 (" ");
+ gimp_help_set_help_data (checkbutton, _("ON: enable vertical color blending. "
+ "OFF: disable vertical color blending."), NULL);
+ gtk_widget_show (checkbutton);
+ gtk_table_attach( GTK_TABLE(table), checkbutton, 1, 2, row, row+1,
+ GTK_FILL, 0, 0, 0 );
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (checkbutton), fiVals->verticalBlendFlag);
+ g_signal_connect (checkbutton, "toggled",
+ G_CALLBACK (on_gboolean_button_update),
+ &fiVals->verticalBlendFlag);
+
+ row++;
+
+ adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row,
+ _("Border Radius:"), SCALE_MIN_WIDTH, SPINBUTTON_MIN_WIDTH,
+ fiVals->borderRadius, 1.0, 10.0, 1.0, 10.0, 0,
+ TRUE, 0, 0,
+ _("radius for picking border colors"),
+ NULL /* help_id */
+ );
+ g_signal_connect (adj, "value-changed",
+ G_CALLBACK (gimp_int_adjustment_update),
+ &fiVals->borderRadius);
+
+ row++;
+
+
+ /* layer combo_box (altSelection) */
+ label = gtk_label_new (_("Set Selection:"));
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_table_attach (GTK_TABLE (table), label, 0, 1, row, row + 1,
+ GTK_FILL, GTK_FILL, 4, 0);
+ gtk_widget_show (label);
+
+ /* layer combo_box (Sample from where to pick the alternative selection */
+ combo = gimp_layer_combo_box_new (p_selectionConstraintFunc, NULL);
+ g_object_set_data (G_OBJECT (combo), "guiStuffPtr", guiStuffPtr);
+ gimp_int_combo_box_prepend (GIMP_INT_COMBO_BOX (combo),
+ GIMP_INT_STORE_VALUE, SELECTION_FROM_VECTORS,
+ GIMP_INT_STORE_LABEL, _("Selection From All Paths"),
+ GIMP_INT_STORE_STOCK_ID, GIMP_STOCK_PATHS,
+ -1);
+ 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);
+
+ initalComboElem = drawable_id;
+ if (fiVals->altSelection == SELECTION_FROM_VECTORS)
+ {
+ initalComboElem = SELECTION_FROM_VECTORS;
+ }
+ else if(fiVals->altSelection == SELECTION_FROM_SVG_FILE)
+ {
+ initalComboElem = SELECTION_FROM_SVG_FILE;
+ }
+ else if(gimp_drawable_is_valid(fiVals->altSelection) == TRUE)
+ {
+ initalComboElem = fiVals->altSelection;
+ }
+ gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), initalComboElem,
+ G_CALLBACK (p_selectionComboCallback),
+ &fiVals->altSelection);
+ gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), initalComboElem);
+
+ 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++;
+
+
+ /* grab vectors button */
+ button = gtk_button_new_with_label (_("Save Paths"));
+ gtk_widget_show (button);
+ gtk_table_attach (GTK_TABLE (table), button, 0, 1, row, row + 1,
+ GTK_FILL, GTK_FILL, 4, 0);
+
+ gimp_help_set_help_data (button, _("Save all pathes as svg vector file."
+ "(use svg file when large or many pathes shall be used)"), NULL);
+ g_signal_connect (G_OBJECT (button), "clicked",
+ G_CALLBACK (on_save_svg_clicked),
+ guiStuffPtr);
+
+ /* the (output) video 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"), NULL);
+ if(strncmp("<?xml", guiStuffPtr->valPtr->selectionSVGFileName, 3) == 0)
+ {
+ g_snprintf(guiStuffPtr->valPtr->selectionSVGFileName
+ , sizeof(guiStuffPtr->valPtr->selectionSVGFileName), "%s"
+ , _("selection.svg"));
+ }
+ gtk_entry_set_text(GTK_ENTRY (entry), guiStuffPtr->valPtr->selectionSVGFileName);
+ g_signal_connect (G_OBJECT (entry), "changed",
+ G_CALLBACK (on_svg_entry_changed),
+ guiStuffPtr);
+
+
+
+ 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);
+
+
+ row++;
+ label = gtk_label_new (" ");
+ guiStuffPtr->msg_label = label;
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_table_attach (GTK_TABLE (table), label, 0, 3, row, row + 1,
+ GTK_FILL, GTK_FILL, 4, 0);
+ gtk_widget_show (label);
+
+
+ p_check_exec_condition_and_set_ok_sesitivity(guiStuffPtr);
+
+ /* Done */
+
+ gtk_widget_show (dialog);
+
+ run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);
+
+ if((run) && (fiVals->altSelection == SELECTION_FROM_VECTORS))
+ {
+ p_save_vectors_to_string(guiStuffPtr);
+ }
+
+ gtk_widget_destroy (dialog);
+
+ if(gap_debug)
+ {
+ printf("guiStuffPtr.2 Svg:%s\n", guiStuffPtr->valPtr->selectionSVGFileName);
+ }
+
+ return run;
+} /* end gap_blend_fill_dialog */
diff --git a/gap/gap_colordiff.c b/gap/gap_colordiff.c
index ddd2139..963cccb 100644
--- a/gap/gap_colordiff.c
+++ b/gap/gap_colordiff.c
@@ -361,3 +361,125 @@ gap_colordiff_simple_guchar(guchar *aPixelPtr
return (colorDiff);
} /* end gap_colordiff_simple_guchar */
+
+
+
+/* ---------------------------------
+ * gap_colordiff_hvmax_GimpHSV
+ * ---------------------------------
+ * returns difference of 2 colors as gdouble value
+ * in range 0.0 (exact match) to 1.0 (maximal difference)
+ */
+gdouble
+gap_colordiff_hvmax_GimpHSV(GimpHSV *aHsvPtr
+ , GimpHSV *bHsvPtr
+ , gboolean debugPrint)
+{
+ gdouble colorDiff;
+ gdouble hDif;
+ gdouble h2Dif;
+ gdouble sDif;
+ gdouble s2Dif;
+ gdouble vDif;
+ gdouble v2Dif;
+ gdouble vMax;
+ gdouble sMax;
+
+
+ hDif = fabs(aHsvPtr->h - bHsvPtr->h);
+ /* normalize hue difference.
+ * hue values represents an angle
+ * where value 0.5 equals 180 degree
+ * and value 1.0 stands for 360 degree that is
+ * equal to 0.0
+ * Hue is maximal different at 180 degree.
+ *
+ * after normalizing, the difference
+ * hDiff value 1.0 represents angle difference of 180 degree
+ */
+ if(hDif > 0.5)
+ {
+ hDif = (1.0 - hDif) * 2.0;
+ }
+ else
+ {
+ hDif = hDif * 2.0;
+ }
+ sDif = fabs(aHsvPtr->s - bHsvPtr->s);
+ vDif = fabs(aHsvPtr->v - bHsvPtr->v);
+
+ sMax = MAX(aHsvPtr->s, bHsvPtr->s);
+ vMax = MAX(aHsvPtr->v, bHsvPtr->v);
+
+ /* increase hue weight when comparing well saturated colors */
+ h2Dif = MIN(1.0, ((4.0 * sMax) * hDif));
+ v2Dif = vDif * vDif;
+ s2Dif = sDif * sDif * sDif;
+
+ colorDiff = MAX(MAX(v2Dif, h2Dif),s2Dif);
+
+
+
+ if(debugPrint)
+ {
+ printf("max HSV: hsv 1/2 (%.3f %.3f %.3f) / (%.3f %.3f %.3f) vMax:%f\n"
+ , aHsvPtr->h
+ , aHsvPtr->s
+ , aHsvPtr->v
+ , bHsvPtr->h
+ , bHsvPtr->s
+ , bHsvPtr->v
+ , vMax
+ );
+ printf("diffHSV: (h2:%.3f h:%.3f s2:%.3f s:%.3f v2:%.3f v:%.3f) max colorDiff:%.5f\n"
+ , h2Dif
+ , hDif
+ , s2Dif
+ , sDif
+ , v2Dif
+ , vDif
+ , colorDiff
+ );
+ }
+
+ return (colorDiff);
+
+} /* end gap_colordiff_hvmax_GimpHSV */
+
+
+
+
+/* ---------------------------------
+ * gap_colordiff_hvmax_guchar
+ * ---------------------------------
+ * returns difference of 2 colors as gdouble value
+ * in range 0.0 (exact match) to 1.0 (maximal difference)
+ * Note:
+ * this procedure uses an HSV colormodel based
+ * Algorithm and calculates difference as max HSV difference.
+ *
+ */
+gdouble
+gap_colordiff_hvmax_guchar(guchar *aPixelPtr
+ , guchar *bPixelPtr
+ , gboolean debugPrint
+ )
+{
+ GimpRGB aRgb;
+ GimpRGB bRgb;
+ GimpHSV aHsv;
+ GimpHSV bHsv;
+
+ gimp_rgba_set_uchar (&aRgb, aPixelPtr[0], aPixelPtr[1], aPixelPtr[2], 255);
+ gimp_rgba_set_uchar (&bRgb, bPixelPtr[0], bPixelPtr[1], bPixelPtr[2], 255);
+
+ gimp_rgb_to_hsv(&aRgb, &aHsv);
+ gimp_rgb_to_hsv(&bRgb, &bHsv);
+
+ return (gap_colordiff_hvmax_GimpHSV(&aHsv
+ , &bHsv
+ , debugPrint
+ ));
+
+} /* end gap_colordiff_hvmax_guchar */
+
diff --git a/gap/gap_colordiff.h b/gap/gap_colordiff.h
index e0c6de5..755414d 100644
--- a/gap/gap_colordiff.h
+++ b/gap/gap_colordiff.h
@@ -139,4 +139,37 @@ gap_colordiff_simple_guchar(guchar *aPixelPtr
);
+
+
+
+
+/* ---------------------------------
+ * gap_colordiff_hvmax_guchar
+ * ---------------------------------
+ * returns difference of 2 colors as gdouble value
+ * in range 0.0 (exact match) to 1.0 (maximal difference)
+ * Note:
+ * this procedure uses an HSV colormodel based
+ * Algorithm and calculates difference as max HSV difference.
+ *
+ */
+gdouble
+gap_colordiff_hvmax_guchar(guchar *aPixelPtr
+ , guchar *bPixelPtr
+ , gboolean debugPrint
+ );
+
+
+/* ---------------------------------
+ * gap_colordiff_hvmax_GimpHSV
+ * ---------------------------------
+ * returns difference of 2 colors as gdouble value
+ * in range 0.0 (exact match) to 1.0 (maximal difference)
+ */
+gdouble
+gap_colordiff_hvmax_GimpHSV(GimpHSV *aHsvPtr
+ , GimpHSV *bHsvPtr
+ , gboolean debugPrint);
+
+
#endif
diff --git a/gap/gap_detail_align_exec.c b/gap/gap_detail_align_exec.c
new file mode 100644
index 0000000..49fd2d2
--- /dev/null
+++ b/gap/gap_detail_align_exec.c
@@ -0,0 +1,821 @@
+/* gap_detail_align_exec.c
+ * This filter locates the position of a small
+ * outside the selction into the selected area.
+ * It was implemented for fixing small pixel defects of my video camera sensor
+ * and is intended to be used as filter when processing video frames
+ * that are shot by such faulty cameras and typically runs
+ * as filtermacro. Therefore the selection can be provided via an external image
+ * or as path vectors via an extrernal SVG file.
+ *
+ * 2011/12/01
+ */
+/* The GIMP -- an image manipulation program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* Revision history
+ * (2011/12/01) 2.7.0 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 <gtk/gtk.h>
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#include "gap_base.h"
+#include "gap_libgapbase.h"
+#include "gap_detail_align_exec.h"
+
+#include "gap-intl.h"
+
+
+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;
+} ParseContext;
+
+
+
+#define DEFAULT_framePhase 1
+
+
+/* --------------------------------------
+ * p_parse_value_gint32
+ * --------------------------------------
+ */
+static gboolean
+p_parse_value_gint32(ParseContext *parseCtx, gint32 *valDestPtr, gint *itemCount)
+{
+ gint64 value64;
+ gchar *endptr;
+
+ value64 = g_ascii_strtoll(parseCtx->parsePtr, &endptr, 10);
+ if(parseCtx->parsePtr == endptr)
+ {
+ /* pointer was not advanced (no int number could be scanned */
+ return (FALSE);
+ }
+ *valDestPtr = value64;
+ parseCtx->parsePtr = endptr;
+ *itemCount +=1;
+ return (TRUE);
+
+} /* end p_parse_value_gint32 */
+
+
+/* --------------------------------
+ * p_parse_coords_p1_and_p2
+ * --------------------------------
+ * parse p1x, p1y, p2x, p2y values into p1 (mandatory) and p2 (ptional) coordinates
+ * and parse keyframe_abs value int *frameNrPtr (optional if present)
+ * multiple occurances are not tolerated.
+ * return TRUE on success.
+ */
+static gboolean
+p_parse_coords_p1_and_p2(ParseContext *parseCtx, PixelCoords *p1, PixelCoords *p2, gint32 *frameNrPtr)
+{
+ gboolean ok;
+ gint px1Count;
+ gint py1Count;
+ gint px2Count;
+ gint py2Count;
+ gint frCount;
+
+ ok = TRUE;
+ px1Count = 0;
+ py1Count = 0;
+ px2Count = 0;
+ py2Count = 0;
+ frCount = 0;
+ while(*parseCtx->parsePtr != '\0')
+ {
+ if (strncmp(parseCtx->parsePtr, "keyframe_abs=\"", strlen("keyframe_abs=\"")) == 0)
+ {
+ parseCtx->parsePtr += strlen("keyframe_abs=\"");
+ ok = p_parse_value_gint32(parseCtx, frameNrPtr, &frCount);
+ }
+ else if (strncmp(parseCtx->parsePtr, "p1x=\"", strlen("p1x=\"")) == 0)
+ {
+ parseCtx->parsePtr += strlen("p1x=\"");
+ ok = p_parse_value_gint32(parseCtx, &p1->px, &px1Count);
+ }
+ else if (strncmp(parseCtx->parsePtr, "p1y=\"", strlen("p1y=\"")) == 0)
+ {
+ parseCtx->parsePtr += strlen("p1y=\"");
+ ok = p_parse_value_gint32(parseCtx, &p1->py, &py1Count);
+ }
+ else if (strncmp(parseCtx->parsePtr, "p2x=\"", strlen("p2x=\"")) == 0)
+ {
+ parseCtx->parsePtr += strlen("p2x=\"");
+ ok = p_parse_value_gint32(parseCtx, &p2->px, &px2Count);
+ }
+ else if (strncmp(parseCtx->parsePtr, "p2y=\"", strlen("p2y=\"")) == 0)
+ {
+ parseCtx->parsePtr += strlen("p2y=\"");
+ ok = p_parse_value_gint32(parseCtx, &p2->py, &py2Count);
+ }
+ else if (strncmp(parseCtx->parsePtr, "/>", strlen("/>")) == 0)
+ {
+ /* stop evaluate when current controlpoint ends */
+ parseCtx->parsePtr += strlen("/>");
+ break;
+ }
+ else if (strncmp(parseCtx->parsePtr, "<controlpoint ", strlen("<controlpoint ")) == 0)
+ {
+ /* stop evaluate when when we run into the next controlpoint */
+ break;
+ }
+ else
+ {
+ parseCtx->parsePtr++;
+ }
+
+ if(ok != TRUE)
+ {
+ break;
+ }
+ }
+
+ if ((ok == TRUE)
+ && (px1Count == 1)
+ && (py1Count == 1)
+ && (frCount <= 1))
+ {
+ p1->valid = TRUE;
+ if ((px2Count == 1)
+ && (py2Count == 1))
+ {
+ p2->valid = 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"
+ ,(int)ok
+ ,(int)px1Count
+ ,(int)py1Count
+ ,(int)px2Count
+ ,(int)py2Count
+ ,(int)frCount
+ ,parseCtx->parsePtr
+ );
+ }
+ return (FALSE);
+
+} /* end p_parse_coords_p1_and_p2 */
+
+
+static void
+p_find_attribute_start(ParseContext *parseCtx)
+{
+ while(*parseCtx->parsePtr != '\0')
+ {
+ if(*parseCtx->parsePtr == '<')
+ {
+ break;
+ }
+ parseCtx->parsePtr++;
+ }
+
+ if(gap_debug)
+ {
+ printf("\nSTRING at parsePtr:%.200s\n"
+ , parseCtx->parsePtr
+ );
+ }
+} /* end p_find_attribute_start */
+
+
+/* --------------------------------
+ * p_parse_xml_controlpoint_coords
+ * --------------------------------
+ *
+ */
+static gboolean
+p_parse_xml_controlpoint_coords(ParseContext *parseCtx)
+{
+ gboolean ok;
+ gint32 frameNr;
+
+ ok = TRUE;
+ frameNr = -1;
+
+ while(*parseCtx->parsePtr != '\0')
+ {
+ p_find_attribute_start(parseCtx);
+
+ if (strncmp(parseCtx->parsePtr, "<controlpoint ", strlen("<controlpoint ")) == 0)
+ {
+ parseCtx->parsePtr += strlen("<controlpoint ");
+
+ if(parseCtx->alingCoords->startCoords.valid == FALSE)
+ {
+ ok = p_parse_coords_p1_and_p2(parseCtx
+ , &parseCtx->alingCoords->startCoords
+ , &parseCtx->alingCoords->startCoords2
+ , &frameNr
+ );
+ }
+ else
+ {
+ ok = p_parse_coords_p1_and_p2(parseCtx
+ , &parseCtx->alingCoords->currCoords
+ , &parseCtx->alingCoords->currCoords2
+ , &frameNr
+ );
+ }
+
+ if(gap_debug)
+ {
+ printf("p_parse_xml_controlpoint_coords: ok:%d, frameNr:%d parseCtx->frameNr:%d\n"
+ ,(int)ok
+ ,(int)frameNr
+ ,(int)parseCtx->frameNr
+ );
+ }
+
+ if(ok != TRUE)
+ {
+ return (FALSE);
+ }
+
+ if ((frameNr == parseCtx->frameNr)
+ && (parseCtx->alingCoords->currCoords2.valid == TRUE))
+ {
+ return(TRUE);
+ }
+ }
+ parseCtx->parsePtr++;
+ }
+
+
+ if ((ok == TRUE)
+ && (parseCtx->alingCoords->currCoords2.valid == TRUE))
+ {
+ /* accept the last controlpoint when no matching frameNr was found */
+ return(TRUE);
+ }
+
+ return(FALSE);
+
+} /* end p_parse_xml_controlpoint_coords */
+
+
+/* -----------------------------------------
+ * p_parse_xml_controlpoint_coords_from_file
+ * -----------------------------------------
+ * parse coords of 1st controlpoint and the controlpoint at frameNr
+ * from the xml file.
+ * return TRUE on success.
+ */
+static gboolean
+p_parse_xml_controlpoint_coords_from_file(const char *filename, gint32 frameNr, AlingCoords *alingCoords)
+{
+ char *textBuffer;
+ gsize lengthTextBuffer;
+
+ ParseContext parseCtx;
+ gboolean parseOk;
+
+ textBuffer = NULL;
+ parseOk = FALSE;
+ if ((g_file_get_contents (filename, &textBuffer, &lengthTextBuffer, NULL) != TRUE)
+ || (textBuffer == NULL))
+ {
+ printf("Couldn't load XML file:%s\n", filename);
+ return(FALSE);
+ }
+
+ parseCtx.parsePtr = textBuffer;
+ parseCtx.alingCoords = alingCoords;
+ parseCtx.frameNr = frameNr;
+
+ parseCtx.alingCoords->startCoords.valid = FALSE;
+ parseCtx.alingCoords->startCoords2.valid = FALSE;
+ parseCtx.alingCoords->currCoords.valid = FALSE;
+ parseCtx.alingCoords->currCoords2.valid = FALSE;
+
+ parseOk = p_parse_xml_controlpoint_coords(&parseCtx);
+
+
+ g_free(textBuffer);
+
+ return (parseOk);
+
+} /* end p_parse_xml_controlpoint_coords_from_file */
+
+
+/* -----------------------------------
+ * p_set_drawable_offsets
+ * -----------------------------------
+ * simple 2-point align via offsets (without rotate and scale)
+ */
+static gint32
+p_set_drawable_offsets(gint32 activeDrawableId, AlingCoords *alingCoords)
+{
+ gdouble px1, py1, px2, py2;
+ gdouble dx, dy;
+ gint offset_x;
+ gint offset_y;
+
+
+ px1 = alingCoords->startCoords.px;
+ py1 = alingCoords->startCoords.py;
+ px2 = alingCoords->currCoords.px;
+ py2 = alingCoords->currCoords.py;
+
+ dx = px2 - px1;
+ dy = py2 - py1;
+
+ /* findout the offsets of the original layer within the source Image */
+ gimp_drawable_offsets(activeDrawableId, &offset_x, &offset_y );
+ gimp_layer_set_offsets(activeDrawableId, offset_x - dx, offset_y - dy);
+
+ return (activeDrawableId);
+
+} /* end p_set_drawable_offsets */
+
+
+/* -----------------------------------
+ * p_exact_align_drawable
+ * -----------------------------------
+ * 4-point alignment including necessary scale and rotate transformation
+ * to match 2 pairs of corresponding coordonates.
+ */
+static gint32
+p_exact_align_drawable(gint32 activeDrawableId, AlingCoords *alingCoords)
+{
+ gdouble px1, py1, px2, py2;
+ gdouble px3, py3, px4, py4;
+ gdouble dx1, dy1, dx2, dy2;
+ gdouble angle1Rad, angle2Rad, angleRad;
+ gdouble len1, len2;
+ gdouble scaleXY;
+ gint32 transformedDrawableId;
+
+ px1 = alingCoords->startCoords.px;
+ py1 = alingCoords->startCoords.py;
+ px2 = alingCoords->startCoords2.px;
+ py2 = alingCoords->startCoords2.py;
+
+ px3 = alingCoords->currCoords.px;
+ py3 = alingCoords->currCoords.py;
+ px4 = alingCoords->currCoords2.px;
+ py4 = alingCoords->currCoords2.py;
+
+ dx1 = px2 - px1;
+ dy1 = py2 - py1;
+ dx2 = px4 - px3;
+ dy2 = py4 - py3;
+
+ /* the angle between the two lines. i.e., the angle layer2 must be clockwise rotatet
+ * in order to overlap with initial start layer1
+ */
+ angle1Rad = 0;
+ angle2Rad = 0;
+ if (dx1 != 0.0)
+ {
+ angle1Rad = atan(dy1 / dx1);
+ }
+ if (dx2 != 0.0)
+ {
+ angle2Rad = atan(dy2 / dx2);
+ }
+ angleRad = angle1Rad - angle2Rad;
+
+ /* the scale factors current layer must be mulitplied by,
+ * in order to fit onto reference start layer.
+ * this is simply the ratio of the two line lenths from the path we created with the 4 points
+ */
+
+ len1 = sqrt((dx1 * dx1) + (dy1 * dy1));
+ len2 = sqrt((dx2 * dx2) + (dy2 * dy2));
+
+ scaleXY = 1.0;
+ if ((len1 != len2)
+ && (len2 != 0.0))
+ {
+ scaleXY = len1 / len2;
+ }
+
+ transformedDrawableId = gimp_drawable_transform_2d(activeDrawableId
+ , px3
+ , py3
+ , scaleXY
+ , scaleXY
+ , angleRad
+ , px1
+ , py1
+ , 0 /* FORWARD (0), TRANSFORM-BACKWARD (1) */
+ , 2 /* INTERPOLATION-CUBIC (2) */
+ , TRUE /* supersample */
+ , 1 /* Maximum recursion level used for supersampling */
+ , 1 /* TRANSFORM-RESIZE-CLIP (1) */
+ );
+
+ if(gap_debug)
+ {
+ printf("p_exact_align_drawable: activeDrawableId:%d transformedDrawableId:%d\n"
+ " p1: %f %f\n"
+ " p2: %f %f\n"
+ " p3: %f %f\n"
+ " p4: %f %f\n"
+ " scale:%f angleRad:%f (degree:%f)\n"
+ ,(int)activeDrawableId
+ ,(int)transformedDrawableId
+ ,(float)px1
+ ,(float)py1
+ ,(float)px2
+ ,(float)py2
+ ,(float)px3
+ ,(float)py3
+ ,(float)px4
+ ,(float)py4
+ ,(float)scaleXY
+ ,(float)angleRad
+ ,(float)(angleRad * 180.0) / G_PI
+ );
+ }
+ return (transformedDrawableId);
+
+} /* end p_exact_align_drawable */
+
+/* -----------------------------------
+ * gap_detail_xml_align
+ * -----------------------------------
+ * This procedure transforms the specified drawable
+ * according controlpoints in an XML file that was recorded via detail tracking.
+ * it picks the relevant controlpoint at framePhase from the XML file
+ * 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.
+ *
+ * returns the drawable id of the resulting transformed layer (or -1 on errors)s
+ */
+gint32
+gap_detail_xml_align(gint32 drawableId, XmlAlignValues *xaVals)
+{
+ gint32 newDrawableId;
+
+ newDrawableId = drawableId;
+ if(gap_debug)
+ {
+ printf("gap_detail_xml_align: START\n"
+ " framePhase:%d moveLogFile:%s\n"
+ , (int)xaVals->framePhase
+ , xaVals->moveLogFile
+ );
+ }
+
+ if(xaVals->framePhase > 1)
+ {
+ gboolean parseOk;
+ AlingCoords alingCoords;
+
+ parseOk =
+ p_parse_xml_controlpoint_coords_from_file(xaVals->moveLogFile
+ , xaVals->framePhase, &alingCoords);
+
+ if(parseOk)
+ {
+ if ((alingCoords.startCoords2.valid == TRUE)
+ && (alingCoords.currCoords2.valid == TRUE))
+ {
+ /* exact align transformation with 2 point pairs including rotation and scaling */
+ newDrawableId = p_exact_align_drawable(drawableId, &alingCoords);
+ }
+ else
+ {
+ /* simple move (to match current recorded point to recorded start point) */
+ newDrawableId = p_set_drawable_offsets(drawableId, &alingCoords);
+ }
+ }
+ else
+ {
+ newDrawableId = -1;
+ }
+
+ }
+
+ return(newDrawableId);
+
+} /* end gap_detail_xml_align */
+
+
+
+
+/* -----------------------------------
+ * gap_detail_xml_align_get_values
+ * -----------------------------------
+ * This procedure is typically called
+ * on the snapshot image created by the Player.
+ * This image has one layer at the first snapshot
+ * and each further snapshot adds one layer on top of the layerstack.
+ *
+ * The start is detected when the image has only one layer.
+ * optionally the numer of layers can be limted
+ * to 2 (or more) layers.
+ */
+void
+gap_detail_xml_align_get_values(XmlAlignValues *xaVals)
+{
+ int l_len;
+
+ /* init default values */
+ xaVals->framePhase = DEFAULT_framePhase;
+ xaVals->moveLogFile[0] = '\0';
+
+ l_len = gimp_get_data_size (GAP_DETAIL_TRACKING_XML_ALIGNER_PLUG_IN_NAME);
+ if (l_len == sizeof(XmlAlignValues))
+ {
+ /* Possibly retrieve data from a previous interactive run */
+ gimp_get_data (GAP_DETAIL_TRACKING_XML_ALIGNER_PLUG_IN_NAME, xaVals);
+
+ if(gap_debug)
+ {
+ printf("gap_detail_xml_align_get_values FOUND data for key:%s\n"
+ , GAP_DETAIL_TRACKING_XML_ALIGNER_PLUG_IN_NAME
+ );
+ }
+ }
+
+ if(gap_debug)
+ {
+ printf("gap_detail_xml_align_get_values:\n"
+ " framePhase:%d moveLogFile:%s\n"
+ , (int)xaVals->framePhase
+ , xaVals->moveLogFile
+ );
+ }
+
+} /* end gap_detail_xml_align_get_values */
+
+
+
+/* ---------------------------
+ * gap_detail_xml_align_dialog
+ * ---------------------------
+ * return TRUE.. OK
+ * FALSE.. in case of Error or cancel
+ */
+gboolean
+gap_detail_xml_align_dialog(XmlAlignValues *xaVals)
+{
+#define SPINBUTTON_ENTRY_WIDTH 70
+#define DETAIL_ALIGN_XML_DIALOG_ARGC 3
+
+ static GapArrArg argv[DETAIL_ALIGN_XML_DIALOG_ARGC];
+ gint ii;
+ gint ii_framePhase;
+
+ ii=0; gap_arr_arg_init(&argv[ii], GAP_ARR_WGT_INT_PAIR); ii_framePhase = ii;
+ argv[ii].label_txt = _("Frame Phase:");
+ argv[ii].help_txt = _("Frame number (phase) to be rendered.");
+ argv[ii].constraint = FALSE;
+ argv[ii].int_min = 1;
+ argv[ii].int_max = 9999;
+ argv[ii].umin = 1;
+ argv[ii].umax = 999999;
+ argv[ii].int_ret = xaVals->framePhase;
+ argv[ii].entry_width = SPINBUTTON_ENTRY_WIDTH;
+ argv[ii].has_default = TRUE;
+ argv[ii].int_default = DEFAULT_framePhase;
+
+
+ ii++; gap_arr_arg_init(&argv[ii], GAP_ARR_WGT_FILESEL);
+ argv[ii].label_txt = _("XML file:");
+ argv[ii].help_txt = _("Name of the xml file that contains the tracked detail coordinates. "
+ " (recorded with the detail tracking feature).");
+ argv[ii].text_buf_len = sizeof(xaVals->moveLogFile);
+ argv[ii].text_buf_ret = &xaVals->moveLogFile[0];
+ argv[ii].entry_width = 400;
+
+
+
+ ii++; gap_arr_arg_init(&argv[ii], GAP_ARR_WGT_DEFAULT_BUTTON);
+ argv[ii].label_txt = _("Default");
+ argv[ii].help_txt = _("Reset all parameters to default values");
+
+ if(TRUE == gap_arr_ok_cancel_dialog(_("Detail Align via XML"),
+ _("Settings :"),
+ DETAIL_ALIGN_XML_DIALOG_ARGC, argv))
+ {
+ xaVals->framePhase = (gint32)(argv[ii_framePhase].int_ret);
+
+ gimp_set_data (GAP_DETAIL_TRACKING_XML_ALIGNER_PLUG_IN_NAME
+ , xaVals, sizeof (XmlAlignValues));
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+} /* end gap_detail_xml_align_dialog */
+
+
+
+/* ---------------------------------- */
+/* ---------------------------------- */
+/* ---------------------------------- */
+/* ---------------------------------- */
+/* ---------------------------------- */
+/* ---------------------------------- */
+
+
+
+/* ------------------------------------------
+ * p_capture_4_vector_points
+ * ------------------------------------------
+ * capture the first 4 points of the 1st stroke in the active path vectors
+ */
+static gint
+p_capture_4_vector_points(gint32 imageId, AlingCoords *alingCoords)
+{
+ gint32 activeVectorsId;
+ PixelCoords *coordPtr[4];
+ gint ii;
+ gint countVaildPoints;
+
+
+ coordPtr[0] = &alingCoords->startCoords;
+ coordPtr[1] = &alingCoords->startCoords2;
+ coordPtr[2] = &alingCoords->currCoords;
+ coordPtr[3] = &alingCoords->currCoords2;
+
+ countVaildPoints = 0;
+ for(ii=0; ii < 4; ii++)
+ {
+ coordPtr[ii]->px = -1;
+ coordPtr[ii]->py = -1;
+ coordPtr[ii]->valid = FALSE;
+
+ }
+
+ activeVectorsId = gimp_image_get_active_vectors(imageId);
+ if(activeVectorsId >= 0)
+ {
+ gint num_strokes;
+ gint *strokes;
+
+ strokes = gimp_vectors_get_strokes (activeVectorsId, &num_strokes);
+ if(strokes)
+ {
+ if(num_strokes > 0)
+ {
+ gdouble *points;
+ gint num_points;
+ gboolean closed;
+ GimpVectorsStrokeType type;
+
+ points = NULL;
+ type = gimp_vectors_stroke_get_points(activeVectorsId, strokes[0],
+ &num_points, &points, &closed);
+
+ if(gap_debug)
+ {
+ gint ii;
+ for(ii=0; ii < MIN(24, num_points); ii++)
+ {
+ printf ("point[%d] = %.3f\n", ii, points[ii]);
+ }
+ }
+
+ if (type == GIMP_VECTORS_STROKE_TYPE_BEZIER)
+ {
+ if(num_points >= 6)
+ {
+ coordPtr[0]->px = points[0];
+ coordPtr[0]->py = points[1];
+ coordPtr[0]->valid = TRUE;
+ countVaildPoints++;
+ }
+ if(num_points >= 12)
+ {
+ coordPtr[1]->px = points[6];
+ coordPtr[1]->py = points[7];
+ coordPtr[1]->valid = TRUE;
+ countVaildPoints++;
+ }
+ if(num_points >= 18)
+ {
+ coordPtr[2]->px = points[12];
+ coordPtr[2]->py = points[13];
+ coordPtr[2]->valid = TRUE;
+ countVaildPoints++;
+ }
+ if(num_points >= 24)
+ {
+ coordPtr[3]->px = points[18];
+ coordPtr[3]->py = points[19];
+ coordPtr[3]->valid = TRUE;
+ countVaildPoints++;
+ }
+ }
+ if(points)
+ {
+ g_free(points);
+ }
+
+ }
+ g_free(strokes);
+ }
+
+ }
+
+ return(countVaildPoints);
+
+} /* end p_capture_4_vector_points */
+
+
+
+/* ------------------------------------------
+ * gap_detail_exact_align_via_4point_path
+ * ------------------------------------------
+ *
+ */
+gint32
+gap_detail_exact_align_via_4point_path(gint32 image_id, gint32 activeDrawableId)
+{
+ AlingCoords alingCoordinates;
+ AlingCoords *alingCoords;
+ gint countVaildPoints;
+ gint32 ret;
+
+ alingCoords = &alingCoordinates;
+ ret = -1;
+
+ countVaildPoints = p_capture_4_vector_points(image_id, alingCoords);
+ if(countVaildPoints == 4)
+ {
+ ret = p_exact_align_drawable(activeDrawableId, alingCoords);
+
+ }
+ else if(countVaildPoints == 2)
+ {
+ alingCoords->currCoords.px = alingCoords->startCoords2.px;
+ alingCoords->currCoords.py = alingCoords->startCoords2.py;
+ alingCoords->currCoords.valid = alingCoords->startCoords2.valid;
+ ret = p_set_drawable_offsets(activeDrawableId, alingCoords);
+ }
+ else
+ {
+ g_message(_("This filter requires a current path with 4 points,"
+ "where point 1 and 2 mark reference positions "
+ "and point 3 and 4 mark postions in the target layer."
+ "It transforms the target layer in a way that "
+ "point3 is moved to point1 and point4 moves to point2."
+ "(this may include rotate an scale transforamtion).\n"
+ "A path with 2 points can be used to move point2 to point1."
+ "(via simple move operation without rotate and scale)"));
+ }
+
+ return(ret);
+
+
+} /* end gap_detail_exact_align_via_4point_path */
+
diff --git a/gap/gap_detail_align_exec.h b/gap/gap_detail_align_exec.h
new file mode 100644
index 0000000..702bd9a
--- /dev/null
+++ b/gap/gap_detail_align_exec.h
@@ -0,0 +1,74 @@
+/* gap_detail_align_exec.h
+ * This filter locates the position of a small
+ * outside the selction into the selected area.
+ * It was implemented for fixing small pixel defects of my video camera sensor
+ * and is intended to be used as filter when processing video frames
+ * that are shot by such faulty cameras and typically runs
+ * as filtermacro. Therefore the selection can be provided via an external image
+ * or as path vectors via an extrernal SVG file.
+ *
+ * 2011/12/01
+ */
+/* The GIMP -- an image manipulation program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* Revision history
+ * (2011/12/01) 2.7.0 hof: created
+ */
+
+#ifndef _GAP_DETAIL_ALIGN_EXEC_H
+#define _GAP_DETAIL_ALIGN_EXEC_H
+
+
+#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 "gap_libgapbase.h"
+#include "gap_image.h"
+#include "gap_layer_copy.h"
+#include "gap_arr_dialog.h"
+
+#include "gap-intl.h"
+
+
+#define GAP_DETAIL_TRACKING_XML_ALIGNER_PLUG_IN_NAME "gap-detail-tracking-xml-aligner"
+#define GAP_EXACT_ALIGNER_PLUG_IN_NAME "gap-exact-aligner"
+
+typedef struct XmlAlignValues {
+ gint32 framePhase;
+ char moveLogFile[1600];
+} XmlAlignValues;
+
+
+
+gint32 gap_detail_xml_align(gint32 drawableId, XmlAlignValues *xaVals);
+void gap_detail_xml_align_get_values(XmlAlignValues *xaVals);
+gboolean gap_detail_xml_align_dialog(XmlAlignValues *xaVals);
+
+gint32 gap_detail_exact_align_via_4point_path(gint32 image_id, gint32 activeDrawableId);
+
+
+
+#endif
diff --git a/gap/gap_detail_tracking_exec.c b/gap/gap_detail_tracking_exec.c
new file mode 100644
index 0000000..2af7617
--- /dev/null
+++ b/gap/gap_detail_tracking_exec.c
@@ -0,0 +1,1530 @@
+/* gap_detail_tracking_exec.c
+ * This filter locates the position of one or 2 small areas
+ * of a reference layer within a target layer and logs the coordinates
+ * as XML file. It is intended to track details in a frame sequence.
+ *
+ * 2011/12/01
+ */
+/* The GIMP -- an image manipulation program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* Revision history
+ * (2011/12/01) 2.7.0 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 <gtk/gtk.h>
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#include "gap_base.h"
+#include "gap_libgapbase.h"
+#include "gap_locate.h"
+#include "gap_locate2.h"
+#include "gap_colordiff.h"
+#include "gap_image.h"
+#include "gap_layer_copy.h"
+#include "gap_detail_tracking_exec.h"
+
+#include "gap-intl.h"
+
+#define DEFAULT_refShapeRadius 15
+#define DEFAULT_targetMoveRadius 70
+#define DEFAULT_loacteColodiffThreshold 0.08
+#define DEFAULT_coordsRelToFrame1 TRUE
+#define DEFAULT_offsX 0
+#define DEFAULT_offsY 0
+#define DEFAULT_offsRotate 0.0
+#define DEFAULT_enableScaling TRUE
+#define DEFAULT_removeMidlayers TRUE
+#define DEFAULT_bgLayerIsReference TRUE
+
+/* -----------------------------------
+ * p_calculate_angle_in_degree
+ * -----------------------------------
+ * calculate angle of the line described by coordinates p1, p2
+ * returns the angle in degree.
+ */
+static gdouble
+p_calculate_angle_in_degree(gint p1x, gint p1y, gint p2x, gint p2y)
+{
+ /* calculate angle in degree
+ * how to rotate an object that follows the line between p1 and p2
+ */
+ gdouble l_a;
+ gdouble l_b;
+ gdouble l_angle_rad;
+ gdouble l_angle;
+
+ l_a = p2x - p1x;
+ l_b = (p2y - p1y) * (-1.0);
+
+ if(l_a == 0)
+ {
+ if(l_b < 0) { l_angle = 90.0; }
+ else { l_angle = 270.0; }
+ }
+ else
+ {
+ l_angle_rad = atan(l_b/l_a);
+ l_angle = (l_angle_rad * 180.0) / G_PI;
+
+ if(l_a < 0)
+ {
+ l_angle = 180 - l_angle;
+ }
+ else
+ {
+ l_angle = l_angle * (-1.0);
+ }
+ }
+
+ if(gap_debug)
+ {
+ printf("p_calc_angle: p1(%d/%d) p2(%d/%d) a=%f, b=%f, angle=%f\n"
+ , (int)p1x, (int)p1y, (int)p2x, (int)p2y
+ , (float)l_a, (float)l_b, (float)l_angle);
+ }
+ return(l_angle);
+
+} /* end p_calculate_angle_in_degree */
+
+
+/* -----------------------------------
+ * p_calculate_scale_factor
+ * -----------------------------------
+ * calculate angle of the line described by coordinates p1, p2
+ * returns the angle in degree.
+ */
+static gdouble
+p_calculate_scale_factor(gint p1x, gint p1y, gint p2x, gint p2y
+ , gint p3x, gint p3y, gint p4x, gint p4y)
+{
+ /* calculate angle in degree
+ * how to rotate an object that follows the line between p1 and p2
+ */
+ gdouble l_a;
+ gdouble l_b;
+ gdouble scaleFactor;
+
+ scaleFactor = 1.0;
+
+ l_a = sqrt(((p2x - p1x) * (p2x - p1x)) + ((p2y - p1y) * (p2y - p1y)));
+ l_b = sqrt(((p4x - p3x) * (p4x - p3x)) + ((p4y - p3y) * (p4y - p3y)));
+
+ if ((l_a >= 0) &&(l_b >= 0))
+ {
+ scaleFactor = l_a / l_b;
+ }
+
+ return(scaleFactor);
+
+} /* end p_calculate_scale_factor */
+
+
+
+
+/* ------------------------------------------
+ * p_capture_2_vector_points
+ * ------------------------------------------
+ * capture the first 2 points of the 1st stroke in the active path vectors
+ */
+static void
+p_capture_2_vector_points(gint32 imageId, PixelCoords *coordPtr, PixelCoords *coordPtr2) {
+ gint32 activeVectorsId;
+ gint32 gx1;
+ gint32 gy1;
+ gint32 gx2;
+ gint32 gy2;
+
+ gx1 = -1;
+ gy1 = -1;
+ gx2 = -1;
+ gy2 = -1;
+
+ activeVectorsId = gimp_image_get_active_vectors(imageId);
+ if(activeVectorsId >= 0)
+ {
+ gint num_strokes;
+ gint *strokes;
+
+ strokes = gimp_vectors_get_strokes (activeVectorsId, &num_strokes);
+ if(strokes)
+ {
+ if(num_strokes > 0)
+ {
+ gdouble *points;
+ gint num_points;
+ gboolean closed;
+ GimpVectorsStrokeType type;
+
+ points = NULL;
+ type = gimp_vectors_stroke_get_points(activeVectorsId, strokes[0],
+ &num_points, &points, &closed);
+
+ if(gap_debug)
+ {
+ gint ii;
+ for(ii=0; ii < MIN(12, num_points); ii++)
+ {
+ printf ("point[%d] = %.3f\n", ii, points[ii]);
+ }
+ }
+
+ if (type == GIMP_VECTORS_STROKE_TYPE_BEZIER)
+ {
+ if(num_points >= 6)
+ {
+ gx1 = points[0];
+ gy1 = points[1];
+ }
+ if(num_points >= 12)
+ {
+ gx2 = points[6];
+ gy2 = points[7];
+ }
+ }
+ if(points)
+ {
+ g_free(points);
+ }
+
+ }
+ g_free(strokes);
+ }
+
+ }
+
+
+ coordPtr->valid = FALSE;
+ coordPtr2->valid = FALSE;
+
+ if((gx1 >= 0) && (gy1 >= 0))
+ {
+ if(gap_debug)
+ {
+ printf("\nPathPoints: x1=%d; y1=%d x2=%d; y2=%d\n"
+ , gx1
+ , gy1
+ , gx2
+ , gy2
+ );
+ }
+
+ coordPtr->px = gx1;
+ coordPtr->py = gy1;
+ coordPtr->valid = TRUE;
+
+ if((gx2 != gx1) || (gy2 != gy1))
+ {
+ coordPtr2->px = gx2;
+ coordPtr2->py = gy2;
+ coordPtr2->valid = TRUE;
+ }
+
+ }
+
+} /* end p_capture_2_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->px = srcCoords->px;
+ dstCoords->py = srcCoords->py;
+}
+
+/* ------------------------------------
+ * p_locate_target
+ * ------------------------------------
+ */
+static void
+p_locate_target(gint32 refLayerId, PixelCoords *refCoords
+ , gint32 targetLayerId, PixelCoords *targetCoords
+ , gint32 locateOffsetX, gint32 locateOffsetY
+ , FilterValues *valPtr, gint32 *lostTraceCount)
+{
+ gdouble colordiffLocate;
+ gint ref_offset_x;
+ gint ref_offset_y;
+ gint target_offset_x;
+ gint target_offset_y;
+ gint32 refX;
+ gint32 refY;
+ gint32 targetX;
+ gint32 targetY;
+
+
+ /* get offsets of the layers within the image */
+ gimp_drawable_offsets (refLayerId, &ref_offset_x, &ref_offset_y);
+ gimp_drawable_offsets (targetLayerId, &target_offset_x, &target_offset_y);
+
+ targetCoords->valid = FALSE;
+ refX = refCoords->px - ref_offset_x;
+ refY = refCoords->py - ref_offset_y;
+ colordiffLocate =
+ gap_locateAreaWithinRadiusWithOffset (refLayerId
+ , refX
+ , refY
+ , valPtr->refShapeRadius
+ , targetLayerId
+ , valPtr->targetMoveRadius
+ , &targetX
+ , &targetY
+ , locateOffsetX
+ , locateOffsetY
+ );
+
+ targetCoords->px = targetX + target_offset_x;
+ targetCoords->py = targetY + target_offset_y;
+
+
+ if (colordiffLocate < valPtr->loacteColodiffThreshold)
+ {
+ /* successful located the deatail in target layer
+ * set target coordinates valid.
+ */
+ targetCoords->valid = TRUE;
+ }
+ else
+ {
+ (*lostTraceCount) += 1;
+ }
+
+ 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"
+ ,(int)refX
+ ,(int)refY
+ ,(int)locateOffsetX
+ ,(int)locateOffsetY
+ ,(int)targetX
+ ,(int)targetY
+ ,(int)targetCoords->px
+ ,(int)targetCoords->py
+ ,(float)colordiffLocate
+ , targetCoords->valid
+ );
+ }
+
+
+} /* end p_locate_target */
+
+
+/* -----------------------------------------
+ * p_write_xml_header
+ * -----------------------------------------
+ * write header for a MovePath XML file
+ */
+static void
+p_write_xml_header(FILE *l_fp, gboolean center, gint width, gint height, 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 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, " <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");
+ }
+ else
+ {
+ fprintf(l_fp, " src_handle=\"GAP_HANDLE_LEFT_TOP\"\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=\"1\" number_of_points=\"%d\" >\n"
+ , (int)numFrames
+ );
+
+} /* end p_write_xml_header */
+
+
+/* -----------------------------------------
+ * p_write_xml_footer
+ * -----------------------------------------
+ */
+static void
+p_write_xml_footer(FILE *l_fp)
+{
+ fprintf(l_fp, " </controlpoints>\n");
+ fprintf(l_fp, "</gimp_gap_move_path_parameters>");
+} /* end p_write_xml_footer */
+
+
+/* -----------------------------------------
+ * p_log_to_file
+ * -----------------------------------------
+ * log controlpoint (logString) to XML file.
+ * An existing XML file is owerwritten, but the controlpoints
+ * of the old content are copied to the newly written XML file generation.
+ *
+ * return TRUE on success, FALSE on errors.
+ */
+static gboolean
+p_log_to_file(const char *filename, const char *logString
+ , gint32 frameNr, gboolean center, gint width, gint height)
+{
+ char *textBuffer;
+ gsize lengthTextBuffer;
+ gint ii;
+ gint beginPos;
+ gint endPos;
+ gint copyPos;
+ gint copySize;
+ gboolean ok;
+ FILE *l_fp;
+
+
+
+ copySize = 0;
+ beginPos = 0;
+ endPos = 0;
+ copyPos = -1;
+ textBuffer = NULL;
+ if ((g_file_test (filename, G_FILE_TEST_EXISTS))
+ && (frameNr > 1))
+ {
+ /* get old content of the XML file and findout the block
+ * of controlpoint lines to be copied when (over)writing the next
+ * generation of the XML file
+ */
+
+ if ((g_file_get_contents (filename, &textBuffer, &lengthTextBuffer, NULL) != TRUE)
+ || (textBuffer == NULL))
+ {
+ printf("Could not load XML file:%s\n", filename);
+ return(FALSE);
+ }
+
+ for(ii=1; ii < lengthTextBuffer; ii++)
+ {
+
+ if (textBuffer[ii] == '\n')
+ {
+ beginPos = -1;
+ }
+
+ if ((textBuffer[ii-1] != ' ') && (textBuffer[ii] == ' '))
+ {
+ beginPos = ii;
+ }
+
+ /* find first controlpoint (as start position of block to copy) */
+ if (strncmp(&textBuffer[ii], "<controlpoint ", strlen("<controlpoint ")) == 0)
+ {
+ if (copyPos < 0)
+ {
+ if (beginPos >= 0)
+ {
+ copyPos = beginPos;
+ }
+ else
+ {
+ copyPos = ii -1;
+ }
+ }
+ }
+
+ /* check end position of the controlpoints block */
+ if (strncmp(&textBuffer[ii], "</controlpoints>", strlen("</controlpoints>")) == 0)
+ {
+ if (beginPos >= 0)
+ {
+ endPos = beginPos;
+ }
+ else
+ {
+ endPos = ii -1;
+ }
+ if (copyPos > 0)
+ {
+ copySize = endPos - copyPos;
+ }
+ break;
+ }
+ }
+ if ((endPos == 0) && (copyPos > 0))
+ {
+ copySize = lengthTextBuffer - copyPos;
+ }
+ }
+
+
+
+ ok = FALSE;
+
+ l_fp = g_fopen(filename, "w+");
+ if(l_fp != NULL)
+ {
+ p_write_xml_header(l_fp, center, width, height, frameNr);
+
+ if (copySize > 0)
+ {
+ fclose(l_fp);
+
+ /* append controlpoints (use binary mode to prevent additional line feeds in Windows environment) */
+ l_fp = g_fopen(filename, "ab");
+ if(l_fp != NULL)
+ {
+ fwrite(&textBuffer[copyPos], copySize, 1, l_fp);
+ fclose(l_fp);
+ l_fp = g_fopen(filename, "a+");
+ }
+ }
+
+ if(l_fp != NULL)
+ {
+ fprintf(l_fp, "%s\n", logString);
+ p_write_xml_footer(l_fp);
+ fclose(l_fp);
+
+ ok = TRUE;
+ }
+ }
+ else
+ {
+ printf("Could not update file:%s", filename);
+ }
+
+ if(textBuffer != NULL)
+ {
+ g_free(textBuffer);
+ }
+
+ return (ok);
+
+} /* end p_log_to_file */
+
+
+
+/* ----------------------------
+ * p_coords_logging
+ * ----------------------------
+ * log coordinates to stdout
+ * or to move-path controlpoint XML file.
+ *
+ */
+static void
+p_coords_logging(gint32 frameNr, PixelCoords *currCoords, PixelCoords *currCoords2
+ , PixelCoords *startCoords, PixelCoords *startCoords2, FilterValues *valPtr
+ , gint32 imageId
+ )
+{
+ gint32 px;
+ gint32 py;
+ gint32 px1;
+ gint32 py1;
+ gint32 px2;
+ gint32 py2;
+ gdouble rotation;
+ gchar *logString;
+ gdouble scaleFactor;
+ gboolean center;
+ gint width;
+ gint height;
+ gint precision_digits;
+ gchar *rotValueAsString;
+
+ if(currCoords->valid != TRUE)
+ {
+ /* do not record invalid coordinates */
+ return;
+ }
+
+ width = gimp_image_width(imageId);
+ height = gimp_image_height(imageId);
+ center = FALSE;
+ if ((valPtr->coordsRelToFrame1)
+ && (valPtr->offsX != 0)
+ && (valPtr->offsY != 0))
+ {
+ center = TRUE;
+ }
+
+ scaleFactor = 1.0;
+ rotation = 0.0;
+ px1 = currCoords->px;
+ py1 = currCoords->py;
+
+ px2 = currCoords2->px;
+ py2 = currCoords2->py;
+
+ if ((valPtr->coordsRelToFrame1)
+ && (startCoords->valid == TRUE))
+ {
+ px1 = startCoords->px -px1;
+ py1 = startCoords->py -py1;
+ }
+
+
+ px = px1 + valPtr->offsX;
+ py = py1 + valPtr->offsY;
+
+ if ((valPtr->coordsRelToFrame1)
+ && (startCoords2->valid == TRUE))
+ {
+ px2 = startCoords2->px -px2;
+ py2 = startCoords2->py -py2;
+
+ }
+
+ if((currCoords2->valid == TRUE)
+ && (startCoords2->valid == TRUE)
+ && (startCoords->valid == TRUE))
+ {
+ gdouble startAngle;
+ gdouble currAngle;
+
+ /* we have 2 valid coordinate points and can calculate rotate compensation
+ * in this case movement offest compensation is the average
+ * of both tracked coordinate points
+ */
+ px = ((px1 + px2) / 2) + valPtr->offsX;
+ py = ((py1 + py2) / 2) + valPtr->offsY;
+
+
+ startAngle = p_calculate_angle_in_degree( startCoords->px
+ , startCoords->py
+ , startCoords2->px
+ , startCoords2->py
+ );
+ currAngle = p_calculate_angle_in_degree( currCoords->px
+ , currCoords->py
+ , currCoords2->px
+ , currCoords2->py
+ );
+ rotation = startAngle - currAngle;
+ if (rotation >= 360.0)
+ {
+ rotation = 360.0 - rotation;
+ }
+ if (rotation <= -360.0)
+ {
+ rotation += 360.0;
+ }
+ scaleFactor = p_calculate_scale_factor( startCoords->px
+ , startCoords->py
+ , startCoords2->px
+ , startCoords2->py
+ , currCoords->px
+ , currCoords->py
+ , currCoords2->px
+ , currCoords2->py
+ );
+ }
+
+ logString = NULL;
+
+ if(currCoords2->valid == TRUE)
+ {
+ /* double point detail coordinate tracking (allows calculation of rotate and scale factors) */
+ gchar *scaleValueAsString;
+
+
+ precision_digits = 7;
+ rotValueAsString = gap_base_gdouble_to_ascii_string(rotation + valPtr->offsRotate, precision_digits);
+ precision_digits = 5;
+ scaleValueAsString = gap_base_gdouble_to_ascii_string(scaleFactor * 100, precision_digits);
+
+ 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\"/>"
+ , px
+ , py
+ , rotValueAsString
+ , scaleValueAsString
+ , scaleValueAsString
+ , frameNr
+ , currCoords->px
+ , currCoords->py
+ , currCoords2->px
+ , currCoords2->py
+ );
+ }
+ else
+ {
+ logString = g_strdup_printf(" <controlpoint px=\"%04d\" py=\"%04d\" rotation=\"%s\" keyframe_abs=\"%d\" p1x=\"%04d\" p1y=\"%04d\" p2x=\"%04d\" p2y=\"%04d\"/>"
+ , px
+ , py
+ , rotValueAsString
+ , frameNr
+ , currCoords->px
+ , currCoords->py
+ , currCoords2->px
+ , currCoords2->py
+ );
+ }
+
+
+ g_free(rotValueAsString);
+ g_free(scaleValueAsString);
+ }
+ else
+ {
+ /* 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\"/>"
+ , px
+ , py
+ , frameNr
+ , currCoords->px
+ , currCoords->py
+ );
+ }
+ else
+ {
+ rotValueAsString = gap_base_gdouble_to_ascii_string(valPtr->offsRotate, precision_digits);
+ precision_digits = 7;
+
+ logString = g_strdup_printf(" <controlpoint px=\"%04d\" py=\"%04d\" rotation=\"%s\" keyframe_abs=\"%d\" p1x=\"%04d\" p1y=\"%04d\"/>"
+ , px
+ , py
+ , rotValueAsString
+ , frameNr
+ , currCoords->px
+ , currCoords->py
+ );
+ g_free(rotValueAsString);
+ }
+
+ }
+
+ if ((valPtr->moveLogFile[0] == '\0')
+ || (valPtr->moveLogFile[0] == '-'))
+ {
+ printf("%s\n", logString);
+ }
+ else
+ {
+ p_log_to_file(&valPtr->moveLogFile[0], logString
+ , frameNr
+ , center
+ , width
+ , height
+ );
+ }
+
+ if (logString)
+ {
+ g_free(logString);
+ }
+
+
+} /* end p_coords_logging */
+
+
+
+/* -------------------------------
+ * p_parse_frame_nr_from_layerId
+ * -------------------------------
+ */
+static gint32
+p_parse_frame_nr_from_layerId(gint32 layerId)
+{
+ char *layername;
+ gint32 frameNr;
+ gint len;
+ gint ii;
+
+
+
+ frameNr = 0;
+
+ layername = gimp_drawable_get_name(layerId);
+ if(layername)
+ {
+ len = strlen(layername);
+ for(ii=1; ii < len; ii++)
+ {
+ if ((layername[ii-1] == '_')
+ && (layername[ii] <= '9')
+ && (layername[ii] >= '0'))
+ {
+ frameNr = g_ascii_strtod(&layername[ii], NULL);
+ break;
+ }
+ }
+ g_free(layername);
+ }
+
+ return (frameNr);
+
+} /* end p_parse_frame_nr_from_layerId */
+
+
+/* -------------------------------
+ * p_get_frameHistInfo
+ * -------------------------------
+ */
+static void
+p_get_frameHistInfo(FrameHistInfo *frameHistInfo)
+{
+ int l_len;
+
+ frameHistInfo->workImageId = -1;
+ frameHistInfo->frameNr = 0;
+ frameHistInfo->startCoords.valid = FALSE;
+ frameHistInfo->startCoords.px = 0;
+ frameHistInfo->startCoords.py = 0;
+ frameHistInfo->lostTraceCount = 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))
+ {
+
+ gimp_get_data(GAP_DETAIL_FRAME_HISTORY_INFO, frameHistInfo);
+
+ if(gap_debug)
+ {
+ printf("p_get_frameHistInfo: %s frameNr:%d px:%d py:%d valid:%d\n"
+ " prevPx:%d prevPy:%d prevValid:%d lostTraceCount:%d\n"
+ , GAP_DETAIL_FRAME_HISTORY_INFO
+ , (int)frameHistInfo->frameNr
+ , (int)frameHistInfo->startCoords.px
+ , (int)frameHistInfo->startCoords.py
+ , (int)frameHistInfo->startCoords.valid
+ , (int)frameHistInfo->prevCoords.px
+ , (int)frameHistInfo->prevCoords.py
+ , (int)frameHistInfo->prevCoords.valid
+ , (int)frameHistInfo->lostTraceCount
+ );
+ }
+
+ }
+
+} /* end p_get_frameHistInfo */
+
+
+/* -------------------------------
+ * p_set_frameHistInfo
+ * -------------------------------
+ * store frame history information
+ * (for the next run in the same gimp session)
+ */
+static void
+p_set_frameHistInfo(FrameHistInfo *frameHistInfo)
+{
+ if(gap_debug)
+ {
+ printf("p_SET_frameHistInfo: %s frameNr:%d px:%d py:%d valid:%d prevPx:%d prevPy:%d prevValid:%d\n\n"
+ , GAP_DETAIL_FRAME_HISTORY_INFO
+ , (int)frameHistInfo->frameNr
+ , (int)frameHistInfo->startCoords.px
+ , (int)frameHistInfo->startCoords.py
+ , (int)frameHistInfo->startCoords.valid
+ , (int)frameHistInfo->prevCoords.px
+ , (int)frameHistInfo->prevCoords.py
+ , (int)frameHistInfo->prevCoords.valid
+
+ );
+ }
+
+ gimp_set_data(GAP_DETAIL_FRAME_HISTORY_INFO, frameHistInfo, sizeof(FrameHistInfo));
+
+} /* end p_set_frameHistInfo */
+
+
+
+/* -------------------------------
+ * p_set_2_vector_points
+ * -------------------------------
+ * remove all strokes from the active path vectors
+ * and set a new stroke containing targetCoords (one or 2 depend on the valid flag)
+ * For better visualisation set guide lines crossing at the first target coords.
+ */
+static void
+p_set_2_vector_points(gint32 imageId, PixelCoords *targetCoords, PixelCoords *targetCoords2)
+{
+ gint32 activeVectorsId;
+ gint newStrokeId;
+
+ gdouble *points;
+ gint num_points;
+ gboolean closed;
+ GimpVectorsStrokeType type;
+
+
+ gimp_image_add_hguide(imageId, targetCoords->py);
+ gimp_image_add_vguide(imageId, targetCoords->px);
+
+
+ activeVectorsId = gimp_image_get_active_vectors(imageId);
+ if(activeVectorsId >= 0)
+ {
+ gint num_strokes;
+ gint *strokes;
+
+ strokes = gimp_vectors_get_strokes (activeVectorsId, &num_strokes);
+ if(strokes)
+ {
+ if(num_strokes > 0)
+ {
+ gint ii;
+ for(ii=0; ii < num_strokes; ii++)
+ {
+ gimp_vectors_remove_stroke(activeVectorsId, strokes[ii]);
+ }
+ }
+ g_free(strokes);
+
+ }
+
+ if (targetCoords->valid)
+ {
+ closed = FALSE;
+ num_points = 6;
+ if (targetCoords2->valid)
+ {
+ num_points = 12;
+ }
+ points = g_new (gdouble, num_points);
+ points[0] = targetCoords->px;
+ points[1] = targetCoords->py;
+ points[2] = targetCoords->px;
+ points[3] = targetCoords->py;
+ points[4] = targetCoords->px;
+ points[5] = targetCoords->py;
+ if(targetCoords2->valid)
+ {
+ points[6] = targetCoords2->px;
+ points[7] = targetCoords2->py;
+ points[8] = targetCoords2->px;
+ points[9] = targetCoords2->py;
+ points[10] = targetCoords2->px;
+ points[11] = targetCoords2->py;
+ }
+
+ type = GIMP_VECTORS_STROKE_TYPE_BEZIER;
+ newStrokeId = gimp_vectors_stroke_new_from_points (activeVectorsId
+ , type
+ , num_points
+ , points
+ , closed
+ );
+ g_free(points);
+ }
+
+
+ }
+
+} /* end p_set_2_vector_points */
+
+
+/* -----------------------------------
+ * gap_track_detail_on_top_layers
+ * -----------------------------------
+ * This procedure is typically called
+ * on the snapshot image created by the Player.
+ * This image has one layer at the first snapshot
+ * and each further snapshot adds one layer on top of the layerstack.
+ *
+ * The start is detected when the image has only one layer.
+ * optionally the numer of layers can be limted
+ * to 2 (or more) layers.
+ */
+gint32
+gap_track_detail_on_top_layers(gint32 imageId, gboolean doProgress, FilterValues *valPtr)
+{
+ gint l_nlayers;
+ gint32 *l_layers_list;
+
+ FrameHistInfo frameHistInfoData;
+ FrameHistInfo *frameHistInfo;
+ PixelCoords currCoords;
+ PixelCoords currCoords2;
+ PixelCoords targetCoords;
+ PixelCoords targetCoords2;
+ gint32 locateOffsetX;
+ gint32 locateOffsetY;
+ gint32 locateOffsetX2;
+ gint32 locateOffsetY2;
+ gint32 lostTraceCount;
+
+ if(gap_debug)
+ {
+ printf("gap_track_detail_on_top_layers: START\n"
+ " refShapeRadius:%d targetMoveRadius:%d locateColordiff:%.4f\n"
+ " coordsRelToFrame1:%d offsX:%d offsY:%d removeMidlayers:%d bgLayerIsReference:%d\n"
+ " moveLogFile:%s\n"
+ , (int)valPtr->refShapeRadius
+ , (int)valPtr->targetMoveRadius
+ , (float)valPtr->loacteColodiffThreshold
+ , (int)valPtr->coordsRelToFrame1
+ , (int)valPtr->offsX
+ , (int)valPtr->offsY
+ , (int)valPtr->removeMidlayers
+ , (int)valPtr->bgLayerIsReference
+ , valPtr->moveLogFile
+ );
+ }
+
+ frameHistInfo = &frameHistInfoData;
+
+ currCoords.valid = FALSE;
+ targetCoords.valid = FALSE;
+
+ l_layers_list = gimp_image_get_layers(imageId, &l_nlayers);
+ if((l_layers_list != NULL)
+ && (l_nlayers > 0))
+ {
+ gint32 topLayerId;
+
+ topLayerId = l_layers_list[0];
+
+ frameHistInfo->frameNr += 1;
+
+ p_get_frameHistInfo(frameHistInfo);
+ locateOffsetX = 0;
+ locateOffsetY = 0;
+ locateOffsetX2 = 0;
+ locateOffsetY2 = 0;
+
+ if (l_nlayers == 1)
+ {
+ p_capture_2_vector_points(imageId, &currCoords, &currCoords2);
+
+ frameHistInfo->lostTraceCount = 0;
+ p_copy_src_to_dst_coords(&currCoords, &frameHistInfo->startCoords);
+ p_copy_src_to_dst_coords(&currCoords2, &frameHistInfo->startCoords2);
+ p_copy_src_to_dst_coords(&currCoords, &frameHistInfo->prevCoords);
+ p_copy_src_to_dst_coords(&currCoords2, &frameHistInfo->prevCoords2);
+
+ frameHistInfo->frameNr = 1;
+ p_coords_logging(frameHistInfo->frameNr
+ , &currCoords
+ , &currCoords2
+ , &frameHistInfo->startCoords
+ , &frameHistInfo->startCoords2
+ , valPtr
+ , imageId
+ );
+ }
+ else
+ {
+ gint32 refLayerId;
+
+ refLayerId = l_layers_list[1];
+ if (valPtr->bgLayerIsReference == TRUE)
+ {
+ refLayerId = l_layers_list[l_nlayers -1];
+ }
+
+
+ if(frameHistInfo->startCoords.valid != TRUE)
+ {
+ p_capture_2_vector_points(imageId, &currCoords, &currCoords2);
+
+ frameHistInfo->lostTraceCount = 0;
+ p_copy_src_to_dst_coords(&currCoords, &frameHistInfo->startCoords);
+ p_copy_src_to_dst_coords(&currCoords2, &frameHistInfo->startCoords2);
+ p_copy_src_to_dst_coords(&currCoords, &frameHistInfo->prevCoords);
+ p_copy_src_to_dst_coords(&currCoords2, &frameHistInfo->prevCoords2);
+
+ frameHistInfo->frameNr = p_parse_frame_nr_from_layerId(refLayerId);
+ p_coords_logging(frameHistInfo->frameNr
+ , &currCoords
+ , &currCoords2
+ , &frameHistInfo->startCoords
+ , &frameHistInfo->startCoords2
+ , valPtr
+ , imageId
+ );
+ }
+ else if (valPtr->bgLayerIsReference == TRUE)
+ {
+ /* when all trackings refere to initial BG layer (that is always kept as reference
+ * for all further frames), we do not capture currCoords
+ * but copy the initial start values.
+ */
+ p_copy_src_to_dst_coords(&frameHistInfo->startCoords, &currCoords);
+ p_copy_src_to_dst_coords(&frameHistInfo->startCoords2, &currCoords2);
+
+ /* locate shall start investigations at matching coordinates of the previous processed frame
+ * because the chance to find the detail near this postion is much greater than near
+ * the start coords in the initial frame.
+ * (note that locate does use the initial frame e.g. the BG layer in this mode,
+ * but without the locateOffsets we might loose track of the detail when it moves outside the targetRadius
+ * and increasing the targetRadius would also result in siginificant longer processing time)
+ */
+ if (frameHistInfo->prevCoords.valid)
+ {
+ locateOffsetX = frameHistInfo->prevCoords.px - frameHistInfo->startCoords.px;
+ locateOffsetY = frameHistInfo->prevCoords.py - frameHistInfo->startCoords.py;
+ }
+ if (frameHistInfo->prevCoords2.valid)
+ {
+ locateOffsetX2 = frameHistInfo->prevCoords2.px - frameHistInfo->startCoords2.px;
+ locateOffsetY2 = frameHistInfo->prevCoords2.py - frameHistInfo->startCoords2.py;
+ }
+ }
+ else
+ {
+ /* tracking is done with reference to the previous layer
+ * therefore refresh capture. (currCoords are set to the targetCoords
+ * that were calculated in previous processing step)
+ */
+ p_capture_2_vector_points(imageId, &currCoords, &currCoords2);
+ }
+
+ lostTraceCount = frameHistInfo->lostTraceCount;
+
+ if (currCoords.valid == TRUE)
+ {
+ p_locate_target(refLayerId
+ , &currCoords
+ , topLayerId
+ , &targetCoords
+ , locateOffsetX
+ , locateOffsetY
+ , valPtr
+ , &frameHistInfo->lostTraceCount
+ );
+ }
+
+ if (currCoords2.valid == TRUE)
+ {
+ p_locate_target(refLayerId
+ , &currCoords2
+ , topLayerId
+ , &targetCoords2
+ , locateOffsetX2
+ , locateOffsetY2
+ , valPtr
+ , &frameHistInfo->lostTraceCount
+ );
+ }
+
+
+ }
+
+
+
+ if (targetCoords.valid == TRUE)
+ {
+ gap_image_remove_all_guides(imageId);
+ p_set_2_vector_points(imageId, &targetCoords, &targetCoords2);
+
+ p_copy_src_to_dst_coords(&targetCoords, &frameHistInfo->prevCoords);
+ p_copy_src_to_dst_coords(&targetCoords2, &frameHistInfo->prevCoords2);
+
+ frameHistInfo->frameNr = p_parse_frame_nr_from_layerId(topLayerId);
+ p_coords_logging(frameHistInfo->frameNr
+ , &targetCoords
+ , &targetCoords2
+ , &frameHistInfo->startCoords
+ , &frameHistInfo->startCoords2
+ , valPtr
+ , imageId
+ );
+
+ }
+
+ if (valPtr->removeMidlayers == TRUE)
+ {
+ gap_image_limit_layers(imageId
+ , 2 /* keepTopLayers */
+ , 1 /* keepBgLayers */
+ );
+ }
+
+ p_set_frameHistInfo(frameHistInfo);
+ g_free(l_layers_list);
+
+ if ((lostTraceCount == 0)
+ && (frameHistInfo->lostTraceCount > 0))
+ {
+ /* trace lost the 1st time, display warning */
+ gap_arr_msg_popup(GIMP_RUN_INTERACTIVE
+ , _("Detail Tracking Stopped. (could not find corresponding detail)"));;
+ }
+ }
+
+ return(imageId);
+
+} /* end gap_track_detail_on_top_layers */
+
+
+
+
+/* ---------------------------------------
+ * gap_track_detail_on_top_layers_lastvals
+ * ---------------------------------------
+ * processing based on last values used in the same gimp session.
+ * (uses defaults when called the 1.st time)
+ * Intended for use in the player
+ */
+gint32
+gap_track_detail_on_top_layers_lastvals(gint32 imageId)
+{
+ gint32 rc;
+ gboolean doProgress;
+ FilterValues fiVals;
+
+ /* clear undo stack */
+ if (gimp_image_undo_is_enabled(imageId))
+ {
+ gimp_image_undo_disable(imageId);
+ }
+
+ doProgress = FALSE;
+ gap_detail_tracking_get_values(&fiVals);
+ rc = gap_track_detail_on_top_layers(imageId, doProgress, &fiVals);
+
+ gimp_image_undo_enable(imageId); /* clear undo stack */
+
+ return(rc);
+
+} /* end gap_track_detail_on_top_layers_lastvals */
+
+
+/* -----------------------------------
+ * gap_detail_tracking_get_values
+ * -----------------------------------
+ * This procedure is typically called
+ * on the snapshot image created by the Player.
+ * This image has one layer at the first snapshot
+ * and each further snapshot adds one layer on top of the layerstack.
+ *
+ * The start is detected when the image has only one layer.
+ * optionally the numer of layers can be limted
+ * to 2 (or more) layers.
+ */
+void
+gap_detail_tracking_get_values(FilterValues *fiVals)
+{
+ int l_len;
+
+ /* init default values */
+ fiVals->refShapeRadius = DEFAULT_refShapeRadius;
+ fiVals->targetMoveRadius = DEFAULT_targetMoveRadius;
+ fiVals->loacteColodiffThreshold = DEFAULT_loacteColodiffThreshold;
+ fiVals->coordsRelToFrame1 = DEFAULT_coordsRelToFrame1;
+ fiVals->offsX = DEFAULT_offsX;
+ fiVals->offsY = DEFAULT_offsY;
+ fiVals->offsRotate = DEFAULT_offsRotate;
+ fiVals->enableScaling = DEFAULT_enableScaling;
+ fiVals->removeMidlayers = DEFAULT_removeMidlayers;
+ fiVals->bgLayerIsReference = DEFAULT_bgLayerIsReference;
+ fiVals->moveLogFile[0] = '\0';
+
+ l_len = gimp_get_data_size (GAP_DETAIL_TRACKING_PLUG_IN_NAME);
+ if (l_len == sizeof(FilterValues))
+ {
+ /* Possibly retrieve data from a previous interactive run */
+ gimp_get_data (GAP_DETAIL_TRACKING_PLUG_IN_NAME, fiVals);
+
+ if(gap_debug)
+ {
+ printf("gap_detail_tracking_get_values FOUND data for key:%s\n"
+ , GAP_DETAIL_TRACKING_PLUG_IN_NAME
+ );
+ }
+ }
+
+ if(gap_debug)
+ {
+ printf("gap_detail_tracking_get_values:\n"
+ " refShapeRadius:%d targetMoveRadius:%d locateColordiff:%.4f\n"
+ " coordsRelToFrame1:%d offsX:%d offsY:%d removeMidlayers:%d bgLayerIsReference:%d\n"
+ " moveLogFile:%s\n"
+ , (int)fiVals->refShapeRadius
+ , (int)fiVals->targetMoveRadius
+ , (float)fiVals->loacteColodiffThreshold
+ , (int)fiVals->coordsRelToFrame1
+ , (int)fiVals->offsX
+ , (int)fiVals->offsY
+ , (int)fiVals->removeMidlayers
+ , (int)fiVals->bgLayerIsReference
+ , fiVals->moveLogFile
+ );
+ }
+
+} /* end gap_detail_tracking_get_values */
+
+
+
+/* --------------------------
+ * gap_detail_tracking_dialog
+ * --------------------------
+ * return TRUE.. OK
+ * FALSE.. in case of Error or cancel
+ */
+gboolean
+gap_detail_tracking_dialog(FilterValues *fiVals)
+{
+#define SPINBUTTON_ENTRY_WIDTH 80
+#define DETAIL_TRACKING_DIALOG_ARGC 12
+
+ static GapArrArg argv[DETAIL_TRACKING_DIALOG_ARGC];
+ gint ii;
+ gint ii_loacteColodiffThreshold;
+ gint ii_refShapeRadius;
+ gint ii_targetMoveRadius;
+ gint ii_coordsRelToFrame1;
+ gint ii_offsX;
+ gint ii_offsY;
+ gint ii_offsRotate;
+ gint ii_enableScaling;
+ gint ii_removeMidlayers;
+ gint ii_bgLayerIsReference;
+
+
+ ii=0; gap_arr_arg_init(&argv[ii], GAP_ARR_WGT_LABEL);
+ argv[0].label_txt = _("This filter requires a current path with one or 2 anchor points\n"
+ "to mark coordinate(s) to be tracked in the target frame(s)");
+
+
+ ii++; gap_arr_arg_init(&argv[ii], GAP_ARR_WGT_FLT_PAIR); ii_loacteColodiffThreshold = ii;
+ argv[ii].constraint = TRUE;
+ argv[ii].label_txt = _("Locate colordiff Thres:");
+ argv[ii].help_txt = _("Colordiff threshold value. Locate fails when average color difference is below this value.");
+ argv[ii].flt_min = 0.0;
+ argv[ii].flt_max = 1.0;
+ argv[ii].flt_ret = fiVals->loacteColodiffThreshold;
+ argv[ii].flt_step = 0.01;
+ argv[ii].flt_digits = 4;
+ argv[ii].entry_width = SPINBUTTON_ENTRY_WIDTH;
+ argv[ii].has_default = TRUE;
+ argv[ii].flt_default = DEFAULT_loacteColodiffThreshold;
+
+
+ ii++; gap_arr_arg_init(&argv[ii], GAP_ARR_WGT_INT_PAIR); ii_refShapeRadius = ii;
+ argv[ii].label_txt = _("Locate Shape Radius:");
+ argv[ii].help_txt = _("The quadratic area surrounding a marked detail coordinate +- this radius "
+ "is considered as reference shape, to be tracked in the target frame(s).");
+ argv[ii].constraint = FALSE;
+ argv[ii].int_min = 1;
+ argv[ii].int_max = 50;
+ argv[ii].umin = 1;
+ argv[ii].umax = 100;
+ argv[ii].int_ret = fiVals->refShapeRadius;
+ argv[ii].entry_width = SPINBUTTON_ENTRY_WIDTH;
+ argv[ii].has_default = TRUE;
+ argv[ii].int_default = DEFAULT_refShapeRadius;
+
+ ii++; gap_arr_arg_init(&argv[ii], GAP_ARR_WGT_INT_PAIR); ii_targetMoveRadius = ii;
+ argv[ii].label_txt = _("Locate Target Move Radius:");
+ argv[ii].help_txt = _("Limits attempts to locate the Detail within this radius.");
+ argv[ii].constraint = FALSE;
+ argv[ii].int_min = 1;
+ argv[ii].int_max = 500;
+ argv[ii].umin = 1;
+ argv[ii].umax = 1000;
+ argv[ii].int_ret = fiVals->targetMoveRadius;
+ argv[ii].entry_width = SPINBUTTON_ENTRY_WIDTH;
+ argv[ii].has_default = TRUE;
+ argv[ii].int_default = DEFAULT_targetMoveRadius;
+
+
+
+ ii++; gap_arr_arg_init(&argv[ii], GAP_ARR_WGT_TOGGLE); ii_coordsRelToFrame1 = ii;
+ argv[ii].label_txt = _("Log Relative Coords:");
+ argv[ii].help_txt = _("ON: Coordinates are logged relative to the first coordinate.\n"
+ "OFF: Coordinates are logged as absolute pixel coordinate values.");
+ argv[ii].constraint = FALSE;
+ argv[ii].int_ret = fiVals->coordsRelToFrame1;
+ argv[ii].has_default = TRUE;
+ argv[ii].int_default = DEFAULT_coordsRelToFrame1;
+
+
+ ii++; gap_arr_arg_init(&argv[ii], GAP_ARR_WGT_TOGGLE); ii_enableScaling = ii;
+ argv[ii].label_txt = _("Log Scaling:");
+ argv[ii].help_txt = _("ON: Calculate scaling and rotation when 2 detail Coordinates are tracked.\n"
+ "OFF: Calculate only rotation and keep orignal size.");
+ argv[ii].int_ret = fiVals->enableScaling;
+ argv[ii].has_default = TRUE;
+ argv[ii].int_default = DEFAULT_enableScaling;
+
+
+
+
+ ii++; gap_arr_arg_init(&argv[ii], GAP_ARR_WGT_TOGGLE); ii_bgLayerIsReference = ii;
+ argv[ii].label_txt = _("BG is Reference:");
+ argv[ii].help_txt = _("ON: Use background layer as reference and foreground layer as target for tracking.\n"
+ "OFF: Use foreground layer as target, and the layer below as reference\n.");
+ argv[ii].int_ret = fiVals->bgLayerIsReference;
+ argv[ii].has_default = TRUE;
+ argv[ii].int_default = DEFAULT_bgLayerIsReference;
+
+
+ ii++; gap_arr_arg_init(&argv[ii], GAP_ARR_WGT_TOGGLE); ii_removeMidlayers = ii;
+ argv[ii].label_txt = _("Remove Middle Layers:");
+ argv[ii].help_txt = _("ON: removes layers (except BG and 2 Layer on top) that are not relevant for detail tracking.\n"
+ "OFF: Keep all layers.");
+ argv[ii].int_ret = fiVals->removeMidlayers;
+ argv[ii].has_default = TRUE;
+ argv[ii].int_default = DEFAULT_removeMidlayers;
+
+
+
+
+
+ ii++; gap_arr_arg_init(&argv[ii], GAP_ARR_WGT_INT_PAIR); ii_offsX = ii;
+ argv[ii].label_txt = _("Const X Offset:");
+ argv[ii].help_txt = _("This value is added when logging captured X coordinates.");
+ argv[ii].constraint = FALSE;
+ argv[ii].int_min = 0;
+ argv[ii].int_max = 2000;
+ argv[ii].umin = 0;
+ argv[ii].umax = 10000;
+ argv[ii].int_ret = fiVals->offsX;
+ argv[ii].entry_width = SPINBUTTON_ENTRY_WIDTH;
+ argv[ii].has_default = TRUE;
+ argv[ii].int_default = DEFAULT_offsX;
+
+
+ ii++; gap_arr_arg_init(&argv[ii], GAP_ARR_WGT_INT_PAIR); ii_offsY = ii;
+ argv[ii].label_txt = _("Const Y Offset:");
+ argv[ii].help_txt = _("This value is added when logging captured Y coordinates.");
+ argv[ii].constraint = FALSE;
+ argv[ii].int_min = 0;
+ argv[ii].int_max = 2000;
+ argv[ii].umin = 0;
+ argv[ii].umax = 10000;
+ argv[ii].int_ret = fiVals->offsY;
+ argv[ii].entry_width = SPINBUTTON_ENTRY_WIDTH;
+ argv[ii].has_default = TRUE;
+ argv[ii].int_default = DEFAULT_offsY;
+
+ ii++; gap_arr_arg_init(&argv[ii], GAP_ARR_WGT_FLT_PAIR); ii_offsRotate = ii;
+ argv[ii].constraint = TRUE;
+ argv[ii].label_txt = _("Const Rotate Offset:");
+ argv[ii].help_txt = _("This value is added when logging rotation values.");
+ argv[ii].flt_min = -360.0;
+ argv[ii].flt_max = 360.0;
+ argv[ii].flt_ret = fiVals->offsRotate;
+ argv[ii].flt_step = 0.1;
+ argv[ii].flt_digits = 4;
+ argv[ii].entry_width = SPINBUTTON_ENTRY_WIDTH;
+ argv[ii].has_default = TRUE;
+ argv[ii].flt_default = DEFAULT_offsRotate;
+
+
+ ii++; gap_arr_arg_init(&argv[ii], GAP_ARR_WGT_FILESEL);
+ argv[ii].label_txt = _("MovePath XML file:");
+ argv[ii].help_txt = _("Name of the file to log the tracked detail coordinates "
+ " as XML parameterfile for later use in the MovePath plug-in.");
+ argv[ii].text_buf_len = sizeof(fiVals->moveLogFile);
+ argv[ii].text_buf_ret = &fiVals->moveLogFile[0];
+ argv[ii].entry_width = 400;
+
+
+
+ ii++; gap_arr_arg_init(&argv[ii], GAP_ARR_WGT_DEFAULT_BUTTON);
+ argv[ii].label_txt = _("Default");
+ argv[ii].help_txt = _("Reset all parameters to default values");
+
+ if(TRUE == gap_arr_ok_cancel_dialog(_("Detail Tracking"),
+ _("Settings :"),
+ DETAIL_TRACKING_DIALOG_ARGC, argv))
+ {
+ fiVals->refShapeRadius = (gint32)(argv[ii_refShapeRadius].int_ret);
+ fiVals->targetMoveRadius = (gint32)(argv[ii_targetMoveRadius].int_ret);
+ fiVals->loacteColodiffThreshold = (gdouble)(argv[ii_loacteColodiffThreshold].flt_ret);
+ fiVals->coordsRelToFrame1 = (gint32)(argv[ii_coordsRelToFrame1].int_ret);
+ fiVals->offsX = (gint32)(argv[ii_offsX].int_ret);
+ fiVals->offsY = (gint32)(argv[ii_offsY].int_ret);
+ fiVals->offsRotate = (gint32)(argv[ii_offsRotate].flt_ret);
+ fiVals->enableScaling = (gint32)(argv[ii_enableScaling].int_ret);
+ fiVals->removeMidlayers = (gint32)(argv[ii_removeMidlayers].int_ret);
+ fiVals->bgLayerIsReference = (gint32)(argv[ii_bgLayerIsReference].int_ret);
+
+ gimp_set_data (GAP_DETAIL_TRACKING_PLUG_IN_NAME, fiVals, sizeof (FilterValues));
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+} /* end gap_detail_tracking_dialog */
+
+
+/* ---------------------------------------
+ * gap_detail_tracking_dialog_cfg_set_vals
+ * ---------------------------------------
+ * return TRUE.. OK
+ * FALSE.. in case of Error or cancel
+ */
+gboolean
+gap_detail_tracking_dialog_cfg_set_vals(gint32 image_id)
+{
+ gboolean rc;
+ FilterValues fiVals;
+
+ gap_detail_tracking_get_values(&fiVals);
+ if(image_id >= 0)
+ {
+ if (fiVals.coordsRelToFrame1)
+ {
+ if(gap_image_is_alive(image_id))
+ {
+ /* default offsets for handle at center */
+ fiVals.offsX = gimp_image_width(image_id) / 2.0;
+ fiVals.offsY = gimp_image_height(image_id) / 2.0;
+ }
+ }
+ }
+
+ rc = gap_detail_tracking_dialog(&fiVals);
+
+ return(rc);
+
+} /* end gap_detail_tracking_dialog_cfg_set_vals */
+
diff --git a/gap/gap_detail_tracking_exec.h b/gap/gap_detail_tracking_exec.h
new file mode 100644
index 0000000..f006e48
--- /dev/null
+++ b/gap/gap_detail_tracking_exec.h
@@ -0,0 +1,119 @@
+/* gap_detail_tracking_exec.h
+ * This filter locates the position of one or 2 small areas
+ * of a reference layer within a target layer and logs the coordinates
+ * as XML file. It is intended to track details in a frame sequence.
+ *
+ * 2011/12/01
+ */
+/* The GIMP -- an image manipulation program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* Revision history
+ * (2011/12/01) 2.7.0 hof: created
+ */
+
+#ifndef _GAP_DETAIL_TRACKING_EXEC_H
+#define _GAP_DETAIL_TRACKING_EXEC_H
+
+
+#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 "gap_libgapbase.h"
+#include "gap_locate.h"
+#include "gap_colordiff.h"
+#include "gap_image.h"
+#include "gap_layer_copy.h"
+#include "gap_arr_dialog.h"
+
+#include "gap-intl.h"
+
+
+
+#define GAP_DETAIL_FRAME_HISTORY_INFO "GAP_DETAIL_FRAME_HISTORY_INFO"
+#define GAP_DETAIL_TRACKING_PLUG_IN_NAME "gap-detail-tracking"
+
+typedef struct FilterValues {
+ gint32 refShapeRadius;
+ gint32 targetMoveRadius;
+ gdouble loacteColodiffThreshold;
+
+ gboolean coordsRelToFrame1; /* subtract coords of frame 1 when logging coords */
+ gint32 offsX; /* add this value when logging coords */
+ gint32 offsY;
+ gdouble offsRotate; /* additional rotation angle, to be added in all controlpoints */
+ gboolean enableScaling; /* on: use rotation and scaling off: roate only */
+ gboolean bgLayerIsReference;
+ gboolean removeMidlayers; /* on: keep 2 top layers and Bg layer, remove other layers off: keep all layers */
+ char moveLogFile[1600];
+} FilterValues;
+
+typedef struct PixelCoords
+{
+ gboolean valid;
+ gint32 px;
+ gint32 py;
+} PixelCoords;
+
+
+typedef struct FrameHistInfo
+{
+ gint32 workImageId;
+ gint32 frameNr; /* last handled frameNr */
+ PixelCoords startCoords; /* coords of first processed frame */
+ PixelCoords startCoords2; /* 2nd detail coords of first processed frame */
+
+ PixelCoords prevCoords; /* coords of the previous processed frame */
+ PixelCoords prevCoords2; /* 2nd detail coords of the previous processed frame */
+
+ gint32 lostTraceCount;
+} FrameHistInfo;
+
+
+
+/* -----------------------------------
+ * gap_track_detail_on_top_layers
+ * -----------------------------------
+ * This procedure is typically called
+ * on the snapshot image created by the Player.
+ * This image has one layer at the first snapshot
+ * and each further snapshot adds one layer on top of the layerstack.
+ *
+ * The start is detected when the image has only one layer.
+ * optionally the numer of layers can be limted
+ * to 2 (or more) layers.
+ */
+gint32 gap_track_detail_on_top_layers(gint32 imageId, gboolean doProgress, FilterValues *valPtr);
+void gap_detail_tracking_get_values(FilterValues *fiVals);
+gboolean gap_detail_tracking_dialog(FilterValues *fiVals);
+
+
+/* procedure variants intended for use in the player plug-in */
+gint32 gap_track_detail_on_top_layers_lastvals(gint32 imageId);
+gboolean gap_detail_tracking_dialog_cfg_set_vals(gint32 imageId);
+
+
+
+#endif
diff --git a/gap/gap_detail_tracking_main.c b/gap/gap_detail_tracking_main.c
new file mode 100644
index 0000000..551ae64
--- /dev/null
+++ b/gap/gap_detail_tracking_main.c
@@ -0,0 +1,719 @@
+/* gap_detail_tracking_main.c
+ * This filter locates the position of a small area of one layer
+ * in another layer.
+ * It was implemented for recording postions as XML input
+ * for the MovePath tool by tracking a detail
+ * 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.
+ *
+ *
+ * Applying the recorded position can compensate unwanted camera moves
+ * when static scenes where shot without using a stativ.
+ * 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.
+ *
+ * 2011/12/01
+ */
+/* The GIMP -- an image manipulation program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* 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_detail_tracking_exec.h"
+#include "gap_detail_align_exec.h"
+#include "gap_arr_dialog.h"
+
+#include "gap-intl.h"
+
+
+
+#define PLUG_IN_NAME GAP_DETAIL_TRACKING_PLUG_IN_NAME
+#define PLUG_IN_NAME_CFG "gap-detail-tracking-config"
+#define PLUG_IN_BINARY "gap_detail_tracking"
+#define PLUG_IN_PRINT_NAME "Detail Tracking"
+#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-plug-detail-tracking"
+
+
+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 */
+
+
+/* Global Variables */
+GimpPlugInInfo PLUG_IN_INFO =
+{
+ NULL, /* init_proc */
+ NULL, /* quit_proc */
+ query, /* query_proc */
+ run /* run_proc */
+};
+
+
+FilterValues fiVals;
+XmlAlignValues xaVals;
+
+
+static const GimpParamDef in_args[] =
+{
+ { GIMP_PDB_INT32, "run-mode", "Interactive, non-interactive" },
+ { GIMP_PDB_IMAGE, "image", "Input image" },
+ { GIMP_PDB_DRAWABLE, "drawable", "ignored" },
+ { GIMP_PDB_INT32, "refShapeRadius", "radius in pixels to identify the reference shape." },
+ { GIMP_PDB_INT32, "targetMoveRadius", "maximal expected movement radius. "
+ "(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, "coordsRelToFrame1", "1 .. substract coords of initial position from all recorded positions."
+ " (e.g. recording starts with px=0 py=0) "
+ "0 .. record absolute positions" },
+ { GIMP_PDB_INT32, "offsX", "fix X offset (is added to all recorded positions)" },
+ { GIMP_PDB_INT32, "offsY", "fix Y offset (is added to all recorded positions)" },
+ { GIMP_PDB_FLOAT, "offsRotate", "fix rotation offset in degree (is added to all rotation values)" },
+ { GIMP_PDB_INT32, "enableScaling", "1: calculate scaling and rotation when 2 points are tracked."
+ "0: calculate only rotation when 2 points are tracked." },
+ { GIMP_PDB_INT32, "bgLayerIsReference", "1: BG layer is used as reference layer for detail tracking, "
+ " (this is typically the 1st frame of the sequence)."
+ "0: The layer below the foreground layer is used as reference."
+ " (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) " }
+};
+
+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 e.g. 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) " }
+};
+
+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" }
+};
+
+
+
+
+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);
+static gint global_number_in_xml_args = G_N_ELEMENTS (in_xml_args);
+static gint global_number_in_exalign_args = G_N_ELEMENTS (in_exalign_args);
+
+
+
+
+/* Functions */
+
+MAIN ()
+
+static void query (void)
+{
+ static GimpLastvalDef lastvals[] =
+ {
+ GIMP_LASTVALDEF_GINT32 (GIMP_ITER_FALSE, fiVals.refShapeRadius, "refShapeRadius"),
+ GIMP_LASTVALDEF_GINT32 (GIMP_ITER_FALSE, fiVals.targetMoveRadius, "targetMoveRadius"),
+ GIMP_LASTVALDEF_GDOUBLE (GIMP_ITER_FALSE, fiVals.loacteColodiffThreshold, "loacteColodiffThreshold"),
+ GIMP_LASTVALDEF_GBOOLEAN (GIMP_ITER_FALSE, fiVals.coordsRelToFrame1, "coordsRelToFrame1"),
+ GIMP_LASTVALDEF_GINT32 (GIMP_ITER_TRUE, fiVals.offsX, "offsX"),
+ GIMP_LASTVALDEF_GINT32 (GIMP_ITER_TRUE, fiVals.offsY, "offsY"),
+ GIMP_LASTVALDEF_GDOUBLE (GIMP_ITER_TRUE, fiVals.offsRotate, "offsRotate"),
+ GIMP_LASTVALDEF_GBOOLEAN (GIMP_ITER_FALSE, fiVals.enableScaling, "enableScaling"),
+ GIMP_LASTVALDEF_GBOOLEAN (GIMP_ITER_FALSE, fiVals.bgLayerIsReference, "bgLayerIsReference"),
+ GIMP_LASTVALDEF_GBOOLEAN (GIMP_ITER_FALSE, fiVals.removeMidlayers, "removeMidlayers"),
+ GIMP_LASTVALDEF_ARRAY (GIMP_ITER_FALSE, fiVals.moveLogFile, "moveLogFileArray"),
+ GIMP_LASTVALDEF_GCHAR (GIMP_ITER_FALSE, fiVals.moveLogFile[0], "moveLogFileChar"),
+
+ };
+
+ 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"),
+
+ };
+
+ /* registration for last values buffer structure (useful for animated filter apply) */
+ gimp_lastval_desc_register(PLUG_IN_NAME,
+ &fiVals,
+ sizeof(fiVals),
+ G_N_ELEMENTS (lastvals),
+ lastvals);
+
+ gimp_lastval_desc_register(GAP_DETAIL_TRACKING_XML_ALIGNER_PLUG_IN_NAME,
+ &xaVals,
+ sizeof(xaVals),
+ G_N_ELEMENTS (xaLastvals),
+ xaLastvals);
+
+ gimp_plugin_domain_register (GETTEXT_PACKAGE, LOCALEDIR);
+
+
+ /* the actual installation of the plugin with configuration dialog */
+ gimp_install_procedure (PLUG_IN_NAME_CFG,
+ "Locate the position of a small area of one layer in another layer.",
+ "This filter operates on 2 layers on top of the layerstack, where "
+ "the topmost layer is the target and the layer below acts as reference layer. "
+ "The position in the reference layer must be provided by the user as active path with one or 2 points. "
+ "For proper operation, both reference and target layer must have exact image size."
+ "The filter loactes the position of the corresponding detail within a specified radius in the target layer "
+ "and adjusts the marked positions on the corresponding detail in the target layer. "
+ "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. "
+ " ",
+ PLUG_IN_AUTHOR,
+ PLUG_IN_COPYRIGHT,
+ GAP_VERSION_WITH_DATE,
+ N_("DetailTracking Config..."),
+ PLUG_IN_IMAGE_TYPES,
+ GIMP_PLUGIN,
+ global_number_in_args,
+ global_number_out_args,
+ in_args,
+ return_vals);
+
+ gimp_install_procedure (PLUG_IN_NAME,
+ "Non-Interactive Locate the position of a small area of one layer in another layer.",
+ "This filter operates on 2 layers on top of the layerstack, where "
+ "the topmost layer is the target and the layer below acts as reference layer. "
+ "The position in the reference layer must be provided by the user as active path with one or 2 points. "
+ "For proper operation, both reference and target layer must have exact image size."
+ "The filter loactes the position of the corresponding detail within a specified radius in the target layer "
+ "and adjusts the marked positions on the corresponding detail in the target layer. "
+ "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. "
+ " ",
+ PLUG_IN_AUTHOR,
+ PLUG_IN_COPYRIGHT,
+ GAP_VERSION_WITH_DATE,
+ N_("DetailTracking"),
+ PLUG_IN_IMAGE_TYPES,
+ GIMP_PLUGIN,
+ global_number_in_args,
+ global_number_out_args,
+ in_args,
+ return_vals);
+
+ /* 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)."
+ " ",
+ PLUG_IN_AUTHOR,
+ PLUG_IN_COPYRIGHT,
+ GAP_VERSION_WITH_DATE,
+ N_("Align Transform via XML file..."),
+ PLUG_IN_IMAGE_TYPES,
+ GIMP_PLUGIN,
+ global_number_in_xml_args,
+ global_number_out_args,
+ 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 "
+ "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."
+ " "
+ " ",
+ PLUG_IN_AUTHOR,
+ PLUG_IN_COPYRIGHT,
+ GAP_VERSION_WITH_DATE,
+ N_("Exact Align via 4-Point Path."),
+ PLUG_IN_IMAGE_TYPES,
+ GIMP_PLUGIN,
+ global_number_in_exalign_args,
+ global_number_out_args,
+ in_exalign_args,
+ return_vals);
+
+
+ {
+ /* Menu names */
+ const char *menupath_image_layer_enhance = N_("<Image>/Video/Layer/Enhance/");
+ const char *menupath_image_layer_transform = N_("<Image>/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_EXACT_ALIGNER_PLUG_IN_NAME, menupath_image_layer_transform);
+ }
+
+} /* end query */
+
+
+static void
+runExactAlign (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 */
+{
+ gint32 image_id = -1;
+ gint32 activeDrawableId = -1;
+ gboolean doFlush;
+
+ /* 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];
+
+ doFlush = TRUE;
+
+ /* 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;
+
+ /* get image and drawable */
+ image_id = param[1].data.d_int32;
+ activeDrawableId = param[2].data.d_drawable;
+
+
+
+ if (status == GIMP_PDB_SUCCESS)
+ {
+
+ gimp_image_undo_group_start (image_id);
+
+
+ /* Run the main function */
+ values[1].data.d_drawable =
+ gap_detail_exact_align_via_4point_path(image_id, activeDrawableId);
+
+ gimp_image_undo_group_end (image_id);
+
+ 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 runExactAlign */
+
+
+
+static void
+runXmlAlign (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 */
+{
+ gint32 image_id = -1;
+ gint32 activeDrawableId = -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];
+
+ 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 */
+ gap_detail_xml_align_get_values(&xaVals);
+
+ /* get image and drawable */
+ image_id = param[1].data.d_int32;
+ activeDrawableId = param[2].data.d_drawable;
+
+
+ /* how are we running today? */
+ switch (run_mode)
+ {
+ case GIMP_RUN_INTERACTIVE:
+ {
+ gboolean dialogOk;
+
+ dialogOk = gap_detail_xml_align_dialog(&xaVals);
+ 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)
+ {
+ xaVals.framePhase = param[3].data.d_int32;
+ xaVals.moveLogFile[0] = '\0';
+ if(param[4].data.d_string != NULL)
+ {
+ g_snprintf(xaVals.moveLogFile, sizeof(xaVals.moveLogFile) -1, "%s", param[4].data.d_string);
+ }
+
+ }
+ 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;
+ }
+ else
+ {
+ if(gap_debug)
+ {
+ // TODO never saw this in tests ...
+ printf("animCallInfo.total_steps: %d current_step:%f\n"
+ ,(int)animCallInfo.total_steps
+ ,(float)animCallInfo.current_step
+ );
+ }
+ if(xaVals.framePhase == 1)
+ {
+ /* apply with constant value framePhase 1 does not make sense
+ * Therefore use current_step as framePhase
+ */
+ xaVals.framePhase = animCallInfo.current_step;
+ }
+
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (status == GIMP_PDB_SUCCESS)
+ {
+
+ gimp_image_undo_group_start (image_id);
+
+
+ /* Run the main function */
+ values[1].data.d_drawable =
+ gap_detail_xml_align(activeDrawableId, &xaVals);
+
+ gimp_image_undo_group_end (image_id);
+
+ 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 runXmlAlign */
+
+
+
+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 activeDrawableId = -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);
+ }
+
+
+
+ if(strcmp(name, GAP_DETAIL_TRACKING_XML_ALIGNER_PLUG_IN_NAME) == 0)
+ {
+ runXmlAlign(name, nparams, param, nreturn_vals, return_vals);
+ return;
+ }
+
+ if(strcmp(name, GAP_EXACT_ALIGNER_PLUG_IN_NAME) == 0)
+ {
+ runExactAlign(name, nparams, param, nreturn_vals, return_vals);
+ return;
+ }
+
+
+ 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 */
+ gap_detail_tracking_get_values(&fiVals);
+
+ /* get image and drawable */
+ image_id = param[1].data.d_int32;
+ activeDrawableId = param[2].data.d_drawable;
+
+
+ /* how are we running today? */
+ switch (run_mode)
+ {
+ case GIMP_RUN_INTERACTIVE:
+ /* detail tracking primary feature is intended to work without dialog interaction
+ * when ivoked by menu or keyboard shortcut using PLUG_IN_NAME.
+ * This plug in also registers with a 2nd variant PLUG_IN_NAME_CFG
+ * where the user can configure the options (for one gimp session)
+ */
+ if(strcmp(name, PLUG_IN_NAME_CFG) ==0)
+ {
+ gboolean dialogOk;
+
+ if (fiVals.coordsRelToFrame1)
+ {
+ /* default offsets for handle at center */
+ fiVals.offsX = gimp_image_width(image_id) / 2.0;
+ fiVals.offsY = gimp_image_height(image_id) / 2.0;
+ }
+
+
+ dialogOk = gap_detail_tracking_dialog(&fiVals);
+ 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.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.moveLogFile[0] = '\0';
+ if(param[13].data.d_string != NULL)
+ {
+ g_snprintf(fiVals.moveLogFile, sizeof(fiVals.moveLogFile) -1, "%s", param[13].data.d_string);
+ }
+
+ }
+ 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)
+ {
+ gulong cache_ntiles;
+ gulong regionTileWidth;
+ gulong regionTileHeight;
+
+ gimp_image_undo_group_start (image_id);
+
+ /* this plug in repeatedly accesses the same tiles in the same pixel regionsarea
+ * therefore tile caching is essential for performance reason
+ * therefore calculate optimal tile cache size (but limit to 300 tiles that should be enogh
+ * in most practical use cases)
+ */
+ regionTileWidth = 1 + (2 * (fiVals.targetMoveRadius + fiVals.refShapeRadius))/ gimp_tile_width() ;
+ regionTileHeight = 1 + (2 * (fiVals.targetMoveRadius + fiVals.refShapeRadius))/ gimp_tile_height() ;
+
+ /* processing may track 2 details in different regions of same size */
+ cache_ntiles = (regionTileWidth * regionTileHeight) * 2;
+
+ gimp_tile_cache_ntiles (MAX(300, cache_ntiles));
+
+ /* Run the main function */
+ values[1].data.d_drawable =
+ gap_track_detail_on_top_layers(image_id, doProgress, &fiVals);
+
+ gimp_image_undo_group_end (image_id);
+
+ 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_edge_detection.c b/gap/gap_edge_detection.c
index 6d198e8..f37a808 100644
--- a/gap/gap_edge_detection.c
+++ b/gap/gap_edge_detection.c
@@ -36,6 +36,8 @@
#include "gap_image.h"
#include "gap_layer_copy.h"
#include "gap_libgapbase.h"
+#include "gap_edge_detection.h"
+#include "gap_pdb_calls.h"
#define OPACITY_LEVEL_UCHAR 50
@@ -56,6 +58,81 @@ typedef struct GapEdgeContext { /* nickname: ectx */
} GapEdgeContext;
+/* ----------------------------------
+ * p_get_debug_coords_from_guides
+ * ----------------------------------
+ * get debug coordinaztes from 1st horizontal and vertical guide crossing.
+ *
+ * note that guides are not relevant for the productive processing
+ * but the 1st guide crossing is used to specify
+ * a coordinate where debug output shall be printed.
+ */
+static void
+p_get_debug_coords_from_guides(gint32 image_id, gint *cx, gint *cy)
+{
+ gint32 guide_id;
+ gint guideRow;
+ gint guideCol;
+
+ guide_id = 0;
+
+ guideRow = -1;
+ guideCol = -1;
+
+ if(image_id < 0)
+ {
+ return;
+ }
+
+
+ while(TRUE)
+ {
+ guide_id = gimp_image_find_next_guide(image_id, guide_id);
+
+ if (guide_id < 1)
+ {
+ break;
+ }
+ else
+ {
+ gint32 orientation;
+
+ orientation = gimp_image_get_guide_orientation(image_id, guide_id);
+ if(orientation != 0)
+ {
+ if(guideCol < 0)
+ {
+ guideCol = gimp_image_get_guide_position(image_id, guide_id);
+ }
+ }
+ else
+ {
+ if(guideRow < 0)
+ {
+ guideRow = gimp_image_get_guide_position(image_id, guide_id);
+ }
+ }
+ }
+
+ }
+
+ *cx = guideCol;
+ *cy = guideRow;
+
+ //if(gap_debug)
+ {
+ printf("image_id:%d guideCol:%d :%d\n"
+ ,(int)image_id
+ ,(int)guideCol
+ ,(int)guideRow
+ );
+ }
+
+} /* end p_get_debug_coords_from_guides */
+
+
+
+
/* ---------------------------------
* p_edgeProcessingForOneRegion
@@ -419,3 +496,309 @@ gint32 gap_edgeDetection(gint32 refDrawableId
return (ectx->edgeDrawableId);
} /* end gap_edgeDetection */
+
+
+
+
+/*
+ * Stuff for the alternative algorithm:
+ *
+ * ----------------------------------------------
+ * Edge detection via Difference to Blurred Copy
+ * ----------------------------------------------
+ *
+ */
+
+
+ /* ---------------------------------
+ * 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"
+ , 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
+ * ---------------------------------
+ */
+ 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;
+
+
+
+
+ imageId = gimp_drawable_get_image(activeDrawableId);
+
+ edgeLayerId = gimp_layer_copy(activeDrawableId);
+ gimp_image_add_layer (imageId, edgeLayerId, 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_add_layer (imageId, blurLayerId, 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 */
+
+
diff --git a/gap/gap_edge_detection.h b/gap/gap_edge_detection.h
index 3379f13..f0676d1 100644
--- a/gap/gap_edge_detection.h
+++ b/gap/gap_edge_detection.h
@@ -1,6 +1,3 @@
-// TODOS: levels of non-black edge pixels are too dark (stretch range...)
-// check colordiff (that seems not work properly on draft locate test image coords x:25 y:23 and x:25 y:24 !!!
-
/* gap_edge_detection.h
* by hof (Wolfgang Hofer)
* 2010/08/08
@@ -58,6 +55,20 @@ gint32 gap_edgeDetection(gint32 refDrawableId
);
+/* ---------------------------------
+ * gap_edgeDetectionByBlurDiff
+ * ---------------------------------
+ * 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
+ */
+gint32
+gap_edgeDetectionByBlurDiff(gint32 activeDrawableId, gdouble blurRadius, gdouble blurResultRadius
+ , gdouble threshold, gint32 shift, gboolean doLevelsAutostretch
+ , gboolean invert);
+
#endif
diff --git a/gap/gap_image.c b/gap/gap_image.c
index ec3a021..90a49b7 100644
--- a/gap/gap_image.c
+++ b/gap/gap_image.c
@@ -293,8 +293,6 @@ gap_image_merge_to_specified_layer(gint32 ref_layer_id, GimpMergeType mergemode)
{
for(l_idx = 0; l_idx < l_nlayers; l_idx++)
{
- gboolean l_visible;
-
if (l_layers_list[l_idx] == ref_layer_id)
{
gimp_drawable_set_visible(l_layers_list[l_idx], TRUE);
@@ -454,3 +452,68 @@ gap_image_remove_invisble_layers(gint32 image_id)
g_free (l_layers_list);
}
} /* end gap_image_remove_invisble_layers */
+
+
+/* ---------------------------------------
+ * gap_image_remove_all_guides
+ * ---------------------------------------
+ */
+void
+gap_image_remove_all_guides(gint32 image_id)
+{
+ gint32 guide_id;
+
+ while(TRUE)
+ {
+ guide_id = 0; /* 0 starts find at 1st guide */
+ guide_id = gimp_image_find_next_guide(image_id, guide_id);
+
+ if (guide_id < 1)
+ {
+ break;
+ }
+ gimp_image_delete_guide(image_id, guide_id);
+ }
+
+
+} /* end gap_image_remove_all_guides */
+
+
+/* ---------------------------------------
+ * gap_image_limit_layers
+ * ---------------------------------------
+ * keepTopLayers number of layers to keep on top of the layerstack (Foreground).
+ * keepBgLayers number of layers to keep on bottom of the layerstack (Background).
+ *
+ * Note that gimp-2.7 or later versions supports layer groups.
+ * this procedure does only check for toplevel layers
+ * and ignores layers that are nested in groups.
+ * e.g. a top level group counts as one single layer
+ * no matter how many layers und subgroups are in the toplevel group.
+ *
+ */
+void
+gap_image_limit_layers(gint32 image_id, gint keepTopLayers, gint keepBgLayers)
+{
+ gint l_nlayers;
+ gint32 *l_layers_list;
+
+ l_layers_list = gimp_image_get_layers(image_id, &l_nlayers);
+ if(l_layers_list != NULL)
+ {
+ int ii;
+
+ for(ii=0; ii < l_nlayers; ii++)
+ {
+ if ((ii >= keepTopLayers)
+ && ((l_nlayers -ii) > keepBgLayers))
+ {
+ gimp_image_remove_layer(image_id, l_layers_list[ii]);
+ }
+ }
+ g_free (l_layers_list);
+
+ }
+} /* end gap_image_limit_layers */
+
+
diff --git a/gap/gap_image.h b/gap/gap_image.h
index 079f9af..f716d5c 100644
--- a/gap/gap_image.h
+++ b/gap/gap_image.h
@@ -54,6 +54,8 @@ gint32 gap_image_merge_to_specified_layer(gint32 ref_layer_id, GimpMergeType
gboolean gap_image_set_selection_from_selection_or_drawable(gint32 image_id, gint32 ref_drawable_id
, gboolean force_from_drawable);
void gap_image_remove_invisble_layers(gint32 image_id);
+void gap_image_remove_all_guides(gint32 image_id);
+void gap_image_limit_layers(gint32 image_id, gint keepTopLayers, gint keepBgLayers);
#endif
diff --git a/gap/gap_locate.c b/gap/gap_locate.c
index 7694dfa..8664d64 100644
--- a/gap/gap_locate.c
+++ b/gap/gap_locate.c
@@ -34,6 +34,7 @@
/* GAP includes */
#include "gap_lib_common_defs.h"
#include "gap_locate.h"
+#include "gap_locate2.h"
#include "gap_colordiff.h"
#include "gap_lib.h"
#include "gap_image.h"
@@ -218,7 +219,6 @@ p_trimReferenceShape(GapLocateContext *lctx)
if(isRefPixelColor)
{
gdouble hDif;
- gdouble sDif;
hDif = fabs(refHsv.h - currentHsv.h);
/* normalize hue difference.
@@ -754,6 +754,12 @@ static void p_locateDetailLoop(GapLocateContext *lctx)
* are best matching (e.g with minimun color difference)
* the return value is the minimum colordifference value
* (in range 0.0 to 1.0 where 0.0 indicates that the compared area is exactly equal)
+ *
+ * NOTE: this procedure is the old implementation and shall not be used in new code.
+ * it calls the more efficient alternative implementation
+ * unless the user explicte sets the gimprc parameter
+ * gap-locate-details-use-old-algorithm yes
+ *
*/
gdouble gap_locateDetailWithinRadius(gint32 refDrawableId
, gint32 refX
@@ -768,6 +774,31 @@ gdouble gap_locateDetailWithinRadius(gint32 refDrawableId
{
GapLocateContext locateContext;
GapLocateContext *lctx;
+
+ gboolean useGapLocateOldAlgo;
+
+
+ useGapLocateOldAlgo =
+ gap_base_get_gimprc_gboolean_value("gap-locate-details-use-old-algorithm", FALSE);
+
+ if(useGapLocateOldAlgo == FALSE)
+ {
+ gdouble avgColordiff;
+
+ avgColordiff =
+ gap_locateAreaWithinRadius(refDrawableId
+ ,refX
+ ,refY
+ ,refShapeRadius
+ ,targetDrawableId
+ ,targetMoveRadius
+ ,targetX
+ ,targetY
+ );
+
+ return(avgColordiff);
+ }
+
/* init context */
lctx = &locateContext;
diff --git a/gap/gap_locate2.c b/gap/gap_locate2.c
new file mode 100644
index 0000000..c6fa3db
--- /dev/null
+++ b/gap/gap_locate2.c
@@ -0,0 +1,604 @@
+/* gap_locate2.c
+ * alternative implementation for locating corresponding pattern in another layer.
+ * by hof (Wolfgang Hofer)
+ * 2011/12/03
+ *
+ */
+/* The GIMP -- an image manipulation program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/* revision history:
+ * version 2.7.0; hof: created
+ */
+
+/* SYTEM (UNIX) includes */
+#include "string.h"
+#include "stdlib.h"
+
+/* GIMP includes */
+#include "gtk/gtk.h"
+#include "libgimp/gimp.h"
+
+/* GAP includes */
+#include "gap_pixelrgn.h"
+#include "gap_locate2.h"
+
+#define MAX_DIFF_VALUE_PER_PIXEL (255.0 + 255.0 + 255.0)
+#define OPACITY_LEVEL_UCHAR 50
+
+extern int gap_debug;
+
+typedef struct Context {
+ gint32 refShapeRadius;
+ gint32 refX;
+ gint32 refY;
+ gint32 bestX;
+ gint32 bestY;
+ gint32 px;
+ gint32 py;
+ gint32 cancelAttemptCount; /* debug information (for development/debug purpose) */
+ gboolean cancelAttemptFlag; /* indicator to cancel current attempt */
+ gboolean isFinishedFlag; /* indicator to cancel all further evaluations */
+ gint32 requiredPixelCount; /* number of pixels minimum required for plausible area comparison (1/4 of full area size)*/
+ gint32 involvedPixelCount; /* number of pixels involved in current comparison attempt */
+ gdouble sumDiffValue; /* summ of the RGB channel differences of the involved pixels in the current attempt */
+ gdouble currentDistance; /* square distance from reference coords to the offset of the current attempt */
+ 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 veryNearDistance; /* square of near radius to stop evaluation when exactly matching area is detected */
+ gdouble bestMatchingAvgColordiff; /* average colordiff at best matching attempt */
+ GimpDrawable *refDrawable;
+ GimpDrawable *targetDrawable;
+
+} Context;
+
+
+/* ---------------------------------
+ * p_calculate_average_colordiff
+ * ---------------------------------
+ * calculate the average color difference for given sum of value differnces and pixel count
+ */
+static inline gdouble
+p_calculate_average_colordiff(gdouble sumDiffValue, gint32 pixelCount)
+{
+ if (pixelCount > 0)
+ {
+ gdouble avgValue;
+ gdouble averageColorDiff;
+
+ avgValue = sumDiffValue / (gdouble)pixelCount;
+ averageColorDiff = avgValue / MAX_DIFF_VALUE_PER_PIXEL;
+ return (averageColorDiff);
+ }
+ return (1.0);
+
+} /* end p_calculate_average_colordiff */
+
+
+/* ---------------------------------
+ * p_compare_regions
+ * ---------------------------------
+ * calculate summary Colorvalues difference for all opaque pixels
+ * in the compared area region.
+ */
+static void
+p_compare_regions (const GimpPixelRgn *refPR
+ ,const GimpPixelRgn *targetPR
+ ,Context *context)
+{
+ guint row;
+ guchar* ref = refPR->data; /* the reference drawable */
+ guchar* target = targetPR->data; /* the target drawable */
+
+// if(gap_debug)
+// {
+// printf("region REF x:%d y:%d w:%d h:%d TARGET x:%d y:%d w:%d h:%d px:%d py:%d\n"
+// , (int)refPR->x
+// , (int)refPR->y
+// , (int)refPR->w
+// , (int)refPR->h
+// , (int)targetPR->x
+// , (int)targetPR->y
+// , (int)targetPR->w
+// , (int)targetPR->h
+// , (int)context->px
+// , (int)context->py
+// );
+// }
+
+
+ for (row = 0; row < targetPR->h; row++)
+ {
+ guint col;
+ guint idxref;
+ guint idxtarget;
+
+ if (row >= refPR->h)
+ {
+ continue;
+ }
+
+ idxref = 0;
+ idxtarget = 0;
+ for(col = 0; col < targetPR->w; col++)
+ {
+ gboolean isCompareable;
+
+ isCompareable = TRUE;
+
+ if (col < refPR->w)
+ {
+ if(refPR->bpp > 3)
+ {
+ if(ref[idxref +3] < OPACITY_LEVEL_UCHAR)
+ {
+ /* transparent reference pixel is not compared */
+ isCompareable = FALSE;
+ }
+ }
+ }
+ else
+ {
+ isCompareable = FALSE;
+ }
+
+
+
+ if(targetPR->bpp > 3)
+ {
+ if(target[idxtarget +3] < OPACITY_LEVEL_UCHAR)
+ {
+ /* transparent target pixel is not compared */
+ isCompareable = FALSE;
+ }
+ }
+
+ if (isCompareable == TRUE)
+ {
+ context->involvedPixelCount += 1;
+ context->sumDiffValue += abs(ref[idxref] - target[idxtarget]);
+ context->sumDiffValue += abs(ref[idxref +1] - target[idxtarget +1]);
+ context->sumDiffValue += abs(ref[idxref +2] - target[idxtarget +2]);
+
+ if (context->sumDiffValue > context->bestMatchingSumDiffValue)
+ {
+ gdouble avgColodiff;
+
+ avgColodiff =
+ p_calculate_average_colordiff(context->sumDiffValue
+ , context->involvedPixelCount
+ );
+ if(avgColodiff > context->bestMatchingAvgColordiff)
+ {
+ /* stop evaluating area at current offset on worse results */
+ context->cancelAttemptFlag = TRUE;
+ context->cancelAttemptCount += 1;
+ return;
+ }
+ }
+
+
+ }
+
+ idxref += refPR->bpp;
+ idxtarget += targetPR->bpp;
+ }
+
+ ref += refPR->rowstride;
+ target += targetPR->rowstride;
+
+ }
+
+} /* end p_compare_regions */
+
+
+/* ---------------------------------
+ * p_calculate_distance_to_ref_coord
+ * ---------------------------------
+ * calculate the (square of) the distance between reference coordinates and px/py
+ */
+static gdouble
+p_calculate_distance_to_ref_coord(Context *context, gint32 px, gint32 py)
+{
+ gdouble squareDistance;
+
+ squareDistance = (context->refX - px) * (context->refX - px);
+ squareDistance += (context->refY - py) * (context->refY - py);
+
+ return(squareDistance);
+
+} /* end p_calculate_distance_to_ref_coord */
+
+
+/* --------------------------------------------
+ * p_attempt_locate_at_current_offset
+ * --------------------------------------------
+ */
+void
+p_attempt_locate_at_current_offset(Context *context, gint32 px, gint32 py)
+{
+ GimpPixelRgn refPR;
+ GimpPixelRgn targetPR;
+ gpointer pr;
+ gint rx1, ry1, rWidth, rHeight;
+ gint tx1, ty1, tWidth, tHeight;
+ gint commonAreaWidth, commonAreaHeight;
+ gboolean isIntersect;
+
+ gint leftShapeRadius;
+ gint upperShapeRadius;
+
+
+ if (context->isFinishedFlag)
+ {
+ return;
+ }
+
+ /* calculate processing relevant interecting reference / target rectangles */
+
+ isIntersect =
+ gimp_rectangle_intersect((context->refX - context->refShapeRadius) /* origin1 */
+ , (context->refY - context->refShapeRadius)
+ , (2 * context->refShapeRadius) /* width1 */
+ , (2 * context->refShapeRadius) /* height1 */
+ ,0
+ ,0
+ ,context->refDrawable->width
+ ,context->refDrawable->height
+ ,&rx1
+ ,&ry1
+ ,&rWidth
+ ,&rHeight
+ );
+ if (!isIntersect)
+ {
+ return;
+ }
+
+ leftShapeRadius = context->refX - rx1;
+ upperShapeRadius = context->refY - ry1;
+
+ isIntersect =
+ gimp_rectangle_intersect((px - leftShapeRadius) /* origin1 */
+ , (py - upperShapeRadius)
+ , rWidth /* width1 */
+ , rHeight /* height1 */
+ ,0
+ ,0
+ ,context->targetDrawable->width
+ ,context->targetDrawable->height
+ ,&tx1
+ ,&ty1
+ ,&tWidth
+ ,&tHeight
+ );
+ if (!isIntersect)
+ {
+ return;
+ }
+
+ commonAreaWidth = tWidth;
+ commonAreaHeight = tHeight;
+
+
+// if(gap_debug)
+// {
+// printf("p_attempt_locate_at: px: %04d py:%04d\n"
+// " rx1:%04d ry1:%04d rWidth:%d rHeight:%d\n"
+// " tx1:%04d ty1:%04d tWidth:%d tHeight:%d\n"
+// " commonAreaWidth:%d commonAreaHeight:%d\n"
+// ,(int)px
+// ,(int)py
+// ,(int)rx1
+// ,(int)ry1
+// ,(int)rWidth
+// ,(int)rHeight
+// ,(int)tx1
+// ,(int)ty1
+// ,(int)tWidth
+// ,(int)tHeight
+// ,(int)commonAreaWidth
+// ,(int)commonAreaHeight
+// );
+// }
+
+ /* rest 'per offset' values in the context */
+ context->cancelAttemptFlag = FALSE;
+ context->sumDiffValue = 0;
+ context->involvedPixelCount = 0;
+ context->currentDistance = p_calculate_distance_to_ref_coord(context, px, py);
+ context->px = px;
+ context->py = py;
+
+ gimp_pixel_rgn_init (&refPR, context->refDrawable, rx1, ry1
+ , commonAreaWidth, commonAreaHeight
+ , FALSE /* dirty */
+ , FALSE /* shadow */
+ );
+
+ gimp_pixel_rgn_init (&targetPR, context->targetDrawable, tx1, ty1
+ , 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))
+ {
+ if (context->cancelAttemptFlag)
+ {
+ break;
+ }
+ else
+ {
+ p_compare_regions(&refPR, &targetPR, context);
+ }
+ }
+
+ if (pr != NULL)
+ {
+ /* NOTE:
+ * early escaping from the loop with pr != NULL
+ * leads to memory leaks due to unbalanced tile ref/unref calls.
+ * the call to gap_gimp_pixel_rgns_unref cals unref on the current tile
+ * (in th same way as gimp_pixel_rgns_process does)
+ * but does not ref another available tile.
+ */
+ gap_gimp_pixel_rgns_unref (pr);
+
+ }
+
+
+
+ if ((context->involvedPixelCount >= context->requiredPixelCount)
+ && (context->sumDiffValue <= context->bestMatchingSumDiffValue))
+ {
+ if((context->sumDiffValue < context->bestMatchingSumDiffValue)
+ || ( context->currentDistance < context->bestMatchingDistance))
+ {
+ context->bestMatchingSumDiffValue = context->sumDiffValue;
+ context->bestMatchingDistance = context->currentDistance;
+ context->bestMatchingPixelCount = context->involvedPixelCount;
+ context->bestX = px;
+ context->bestY = py;
+ context->bestMatchingAvgColordiff =
+ p_calculate_average_colordiff(context->bestMatchingSumDiffValue
+ , context->bestMatchingPixelCount
+ );
+
+ if(gap_debug)
+ {
+ printf("FOUND: bestX:%d bestY:%d squareDist:%d\n"
+ " sumDiffValues:%d pixelCount:%d bestMatchingAvgColordiff:%.5f\n"
+ , (int)context->bestX
+ , (int)context->bestY
+ , (int)context->bestMatchingDistance
+ , (int)context->bestMatchingSumDiffValue
+ , (int)context->bestMatchingPixelCount
+ , (float)context->bestMatchingAvgColordiff
+ );
+ }
+
+ if ((context->currentDistance <= context->veryNearDistance)
+ && (context->sumDiffValue == 0))
+ {
+ /* stop all further attempts on exact matching area when near reference origin */
+ context->isFinishedFlag = TRUE;
+ }
+ }
+ }
+
+
+} /* end p_attempt_locate_at_current_offset */
+
+
+
+/* --------------------------------------------
+ * gap_locateAreaWithinRadiusWithOffset
+ * --------------------------------------------
+ * processing starts at reference coords + offest
+ * and continues outwards upto targetMoveRadius for 4 quadrants.
+ *
+ * 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 comaring full white agains full black area)
+ */
+gdouble
+gap_locateAreaWithinRadiusWithOffset(gint32 refDrawableId
+ , gint32 refX
+ , gint32 refY
+ , gint32 refShapeRadius
+ , gint32 targetDrawableId
+ , gint32 targetMoveRadius
+ , gint32 *targetX
+ , gint32 *targetY
+ , gint32 offsetX
+ , gint32 offsetY
+ )
+{
+ Context contextData;
+ Context *context;
+ gdouble averageColorDiff;
+ gboolean isFinishedFlag;
+ gint idx;
+ gint idy;
+ gdouble maxPixelCount;
+
+
+ *targetX = refX;
+ *targetY = refY;
+
+ /* init Context */
+ context = &contextData;
+ context->refShapeRadius = refShapeRadius;
+ context->refX = refX;
+ context->refY = refY;
+ context->bestX = refX;
+ context->bestY = refY;
+ context->cancelAttemptCount = 0;
+ context->cancelAttemptFlag = FALSE;
+ context->isFinishedFlag = FALSE;
+ context->requiredPixelCount = MAX(1, ((refShapeRadius * refShapeRadius) / 4));
+ context->involvedPixelCount = 0;
+ context->sumDiffValue = 0;
+ context->currentDistance = 0;
+ context->bestMatchingPixelCount = 0;
+ context->veryNearDistance = (2 * 2);
+
+ 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->bestMatchingSumDiffValue = maxPixelCount * MAX_DIFF_VALUE_PER_PIXEL;
+ context->bestMatchingDistance = maxPixelCount;
+ context->bestMatchingAvgColordiff = 1.0;
+
+ averageColorDiff = 1.0;
+
+ for(idx = 0; idx <= targetMoveRadius; idx ++)
+ {
+ if (context->isFinishedFlag)
+ {
+ break;
+ }
+
+ for(idy = 0; idy <= targetMoveRadius; idy++)
+ {
+ gint32 dx;
+ gint32 dy;
+
+ dx = idx;
+ dy = idy;
+ p_attempt_locate_at_current_offset(context, (offsetX + refX) + dx, (offsetY + refY) +dy);
+ if (isFinishedFlag)
+ {
+ break;
+ }
+
+ if (idx > 0)
+ {
+ p_attempt_locate_at_current_offset(context, (offsetX + refX) - dx, (offsetY + refY) +dy);
+ if (context->isFinishedFlag)
+ {
+ break;
+ }
+ }
+
+ if (idy > 0)
+ {
+ p_attempt_locate_at_current_offset(context, (offsetX + refX) + dx, (offsetY + refY) -dy);
+ if (context->isFinishedFlag)
+ {
+ break;
+ }
+ }
+
+ if ((idx > 0) && (idy > 0))
+ {
+ p_attempt_locate_at_current_offset(context, (offsetX + refX) - dx, (offsetY + refY) -dy);
+ if (context->isFinishedFlag)
+ {
+ break;
+ }
+ }
+
+ }
+ }
+
+ if (context->bestMatchingPixelCount > 0)
+ {
+ *targetX = context->bestX;
+ *targetY = context->bestY;
+ averageColorDiff = context->bestMatchingAvgColordiff;
+
+ if(gap_debug)
+ {
+ printf("gap_locateAreaWithinRadiusWithOffset Result: bestX:%d bestY:%d averageColorDiff:%.5f\n"
+ " sumDiffValues:%d pixelCount:%d\n"
+ " refX:%d refY:%d cancelAttemptCount:%d\n"
+ , (int)context->bestX
+ , (int)context->bestY
+ , (float)averageColorDiff
+ , (int)context->bestMatchingSumDiffValue
+ , (int)context->bestMatchingPixelCount
+ , (int)context->refX
+ , (int)context->refY
+ , (int)context->cancelAttemptCount
+ );
+ }
+ }
+
+
+ if(context->refDrawable != NULL)
+ {
+ gimp_drawable_detach(context->refDrawable);
+ }
+ if(context->targetDrawable != NULL)
+ {
+ gimp_drawable_detach(context->targetDrawable);
+ }
+
+ return (averageColorDiff);
+
+} /* end gap_locateAreaWithinRadiusWithOffset */
+
+
+/* --------------------------------------------
+ * gap_locateAreaWithinRadius
+ * --------------------------------------------
+ * processing starts at reference coords and continues
+ * outwards upto targetMoveRadius for 4 quadrants.
+ *
+ * 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 comaring full white agains full black area)
+ */
+gdouble
+gap_locateAreaWithinRadius(gint32 refDrawableId
+ , gint32 refX
+ , gint32 refY
+ , gint32 refShapeRadius
+ , gint32 targetDrawableId
+ , gint32 targetMoveRadius
+ , gint32 *targetX
+ , gint32 *targetY
+ )
+{
+ gdouble avgColordiff;
+
+ avgColordiff =
+ gap_locateAreaWithinRadiusWithOffset(refDrawableId
+ , refX
+ , refY
+ , refShapeRadius
+ , targetDrawableId
+ , targetMoveRadius
+ , targetX
+ , targetY
+ , 0
+ , 0
+ );
+ return (avgColordiff);
+
+} /* end gap_locateAreaWithinRadius */
diff --git a/gap/gap_locate2.h b/gap/gap_locate2.h
new file mode 100644
index 0000000..dccbda2
--- /dev/null
+++ b/gap/gap_locate2.h
@@ -0,0 +1,96 @@
+/* gap_locate.h
+ * alternative implementation for locating corresponding pattern in another layer.
+ * by hof (Wolfgang Hofer)
+ * 2011/12/03
+ *
+ */
+/* The GIMP -- an image manipulation program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/* revision history:
+ * version 2.7.0; hof: created
+ */
+
+#ifndef _GAP_LOCATE2_H
+#define _GAP_LOCATE2_H
+
+/* SYTEM (UNIX) includes */
+#include <stdio.h>
+#include <stdlib.h>
+
+/* GIMP includes */
+#include "gtk/gtk.h"
+#include "libgimp/gimp.h"
+
+
+/* ----------------------------------------
+ * gap_locateAreaWithinRadius
+ * ----------------------------------------
+ *
+ * the locateAreaWithinRadius procedure takes a referenced detail
+ * specified via refX/Y coordinate, refShapeRadius within a
+ * reference drawable
+ * and tries to locate the same (or similar) detail coordinates
+ * in the target Drawable.
+ *
+ * This is done by comparing rgb pixel values of the areas at refShapeRadius
+ * in the corresponding target Drawable in a loop while
+ * varying offsets within targetMoveRadius.
+ * the targetX/Y koords are picked at those offsets where the compared areas
+ * are best matching (e.g with minimun color difference)
+ * the return value is the minimum colrdifference value
+ * (in range 0.0 to 1.0 where 0.0 indicates that the compared area is exactly equal)
+ *
+ */
+gdouble
+gap_locateAreaWithinRadius(gint32 refDrawableId
+ , gint32 refX
+ , gint32 refY
+ , gint32 refShapeRadius
+ , gint32 targetDrawableId
+ , gint32 targetMoveRadius
+ , gint32 *targetX
+ , gint32 *targetY
+ );
+
+
+/* --------------------------------------------
+ * gap_locateAreaWithinRadiusWithOffset
+ * --------------------------------------------
+ * processing starts at reference coords + offest
+ * and continues outwards upto targetMoveRadius for 4 quadrants.
+ *
+ * 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 comaring full white agains full black area)
+ */
+gdouble
+gap_locateAreaWithinRadiusWithOffset(gint32 refDrawableId
+ , gint32 refX
+ , gint32 refY
+ , gint32 refShapeRadius
+ , gint32 targetDrawableId
+ , gint32 targetMoveRadius
+ , gint32 *targetX
+ , gint32 *targetY
+ , gint32 offsetX
+ , gint32 offsetY
+ );
+
+
+#endif
diff --git a/gap/gap_mov_dialog.h b/gap/gap_mov_dialog.h
index 23f0577..d52c2a6 100755
--- a/gap/gap_mov_dialog.h
+++ b/gap/gap_mov_dialog.h
@@ -46,7 +46,8 @@
#define GAP_MOVPATH_XML_FILENAME_MAX_LENGTH 1024
#define GAP_MOVEPATH_GIMPRC_LOG_RENDER_PARAMS "video-move-path-log-render-params"
-
+#define GAP_MOVEPATH_GIMPRC_ROTATE_THRESHOLD "video-move-path-rotate-threshold"
+#define GAP_MOVEPATH_DEFAULT_ROTATE_THRESHOLD 0.015
typedef enum
{
@@ -261,6 +262,8 @@ typedef struct {
GapBlueboxGlobalParams *bbp;
GapBlueboxGlobalParams *bbp_pv;
+ gdouble rotate_threshold;
+
} GapMovValues;
diff --git a/gap/gap_mov_exec.c b/gap/gap_mov_exec.c
index f4de61e..af62297 100755
--- a/gap/gap_mov_exec.c
+++ b/gap/gap_mov_exec.c
@@ -1390,19 +1390,21 @@ p_log_current_render_params(GapMovData *mov_ptr, GapMovCurrent *cur_ptr)
" currX:%f currY:%f\n"
" Width:%f Height:%f\n"
" Opacity:%f Rotate:%f clip_to_img:%d force_visibility:%d\n"
- " src_stepmode:%d handleX:%d handleY:%d currSelFeatherRadius:%f\n",
+ " src_stepmode:%d handleX:%d handleY:%d currSelFeatherRadius:%f rotate_threshold:%f\n",
cur_ptr->dst_frame_nr, (int)val_ptr->twix, cur_ptr->src_layer_idx,
- cur_ptr->currX, cur_ptr->currY,
- cur_ptr->currWidth,
- cur_ptr->currHeight,
- cur_ptr->currOpacity,
- cur_ptr->currRotation,
+ (float)cur_ptr->currX,
+ (float)cur_ptr->currY,
+ (float)cur_ptr->currWidth,
+ (float)cur_ptr->currHeight,
+ (float)cur_ptr->currOpacity,
+ (float)cur_ptr->currRotation,
val_ptr->clip_to_img,
val_ptr->src_force_visible,
val_ptr->src_stepmode,
cur_ptr->l_handleX,
cur_ptr->l_handleY,
- cur_ptr->currSelFeatherRadius
+ (float)cur_ptr->currSelFeatherRadius,
+ (float)val_ptr->rotate_threshold
);
printf(" Perspective Factors: [0] %.3f %.3f [1] %.3f %.3f [2] %.3f %.3f [3] %.3f %.3f\n"
@@ -3854,6 +3856,26 @@ void gap_mov_exec_set_handle_offsets(GapMovValues *val_ptr, GapMovCurrent *cur_p
} /* end gap_mov_exec_set_handle_offsets */
+/* ------------------------------------
+ * gap_mov_exec_new_GapMovValues
+ * ------------------------------------
+ */
+gdouble
+gap_mov_exec_get_default_rotate_threshold()
+{
+ gdouble rotate_threshold;
+
+
+ rotate_threshold =
+ gap_base_get_gimprc_gdouble_value (GAP_MOVEPATH_GIMPRC_ROTATE_THRESHOLD
+ , GAP_MOVEPATH_DEFAULT_ROTATE_THRESHOLD
+ , 0.0 /* gdouble min_value */
+ , 1.0 /* gdouble max_value */
+ );
+
+ return (rotate_threshold);
+} /* end gap_mov_exec_get_default_rotate_threshold */
+
/* ------------------------------------
* gap_mov_exec_new_GapMovValues
@@ -3866,6 +3888,7 @@ GapMovValues *gap_mov_exec_new_GapMovValues()
pvals = g_new (GapMovValues, 1);
pvals->version = GAP_MOV_INT_VERSION;
+ pvals->rotate_threshold = gap_mov_exec_get_default_rotate_threshold();
pvals->recordedFrameWidth = 0; /* witdh of the frame (at recording time of the move path settings) */
pvals->recordedFrameHeight = 0; /* height of the frame (at recording time of the move path settings) */
pvals->recordedObjWidth = 0;
diff --git a/gap/gap_mov_exec.h b/gap/gap_mov_exec.h
index 53e1948..0886eb0 100644
--- a/gap/gap_mov_exec.h
+++ b/gap/gap_mov_exec.h
@@ -55,6 +55,7 @@ gint gap_mov_exec_gap_load_pointfile(char *filename, GapMovValues *pvals);
void gap_mov_exec_calculate_rotate_follow(GapMovValues *pvals, gdouble startangle);
void gap_mov_exec_set_handle_offsets(GapMovValues *val_ptr, GapMovCurrent *cur_ptr);
void gap_mov_exec_query(GapMovValues *val_ptr, GapAnimInfo *ainfo_ptr, GapMovQuery *mov_query);
+gdouble gap_mov_exec_get_default_rotate_threshold();
GapMovValues *gap_mov_exec_new_GapMovValues();
diff --git a/gap/gap_mov_render.c b/gap/gap_mov_render.c
index e0173e6..ea49cb8 100644
--- a/gap/gap_mov_render.c
+++ b/gap/gap_mov_render.c
@@ -739,7 +739,7 @@ gap_mov_render_render(gint32 image_id, GapMovValues *val_ptr, GapMovCurrent *cur
printf("gap_mov_render_render: frame/layer: %ld/%ld X=%f, Y=%f\n"
" Width=%f Height=%f\n"
" Opacity=%f Rotate=%f clip_to_img = %d force_visibility = %d\n"
- " src_stepmode = %d\n"
+ " src_stepmode = %d rotate_threshold=%.7f\n"
" singleMovObjLayerId=%d singleMovObjIMAGEId=%d (frame)image_id=%d\n",
cur_ptr->dst_frame_nr, cur_ptr->src_layer_idx,
cur_ptr->currX, cur_ptr->currY,
@@ -750,6 +750,7 @@ gap_mov_render_render(gint32 image_id, GapMovValues *val_ptr, GapMovCurrent *cur
val_ptr->clip_to_img,
val_ptr->src_force_visible,
val_ptr->src_stepmode,
+ val_ptr->rotate_threshold,
cur_ptr->singleMovObjLayerId,
gimp_drawable_get_image(cur_ptr->singleMovObjLayerId),
image_id
@@ -995,7 +996,8 @@ gap_mov_render_render(gint32 image_id, GapMovValues *val_ptr, GapMovCurrent *cur
}
- if((cur_ptr->currRotation > 0.5) || (cur_ptr->currRotation < -0.5))
+ if((cur_ptr->currRotation > val_ptr->rotate_threshold)
+ || (cur_ptr->currRotation < (0.0 - val_ptr->rotate_threshold)))
{
gboolean l_interpolation;
diff --git a/gap/gap_mov_xml_par.c b/gap/gap_mov_xml_par.c
index 0aa15d3..9fbf402 100755
--- a/gap/gap_mov_xml_par.c
+++ b/gap/gap_mov_xml_par.c
@@ -119,6 +119,7 @@
#define GAP_MOVPATH_XML_TOKEN_CONTROLPOINTS "controlpoints"
#define GAP_MOVPATH_XML_TOKEN_CURRENT_POINT "current_point"
#define GAP_MOVPATH_XML_TOKEN_NUMBER_OF_POINTS "number_of_points"
+#define GAP_MOVPATH_XML_TOKEN_ROTATE_THRESHOLD "rotate_threshold"
#define GAP_MOVPATH_XML_TOKEN_CONTROLPOINT "controlpoint"
#define GAP_MOVPATH_XML_TOKEN_PX "px"
#define GAP_MOVPATH_XML_TOKEN_PY "py"
@@ -819,6 +820,10 @@ p_xml_parse_element_controlpoints(const gchar *element_name,
{
userDataPtr->isParseOk = gap_xml_parse_value_gint(*value_cursor, &userDataPtr->pvals->point_idx);
}
+ else if (strcmp (*name_cursor, GAP_MOVPATH_XML_TOKEN_ROTATE_THRESHOLD) == 0)
+ {
+ userDataPtr->isParseOk = gap_xml_parse_value_gdouble(*value_cursor, &userDataPtr->pvals->rotate_threshold);
+ }
else if (strcmp (*name_cursor, GAP_MOVPATH_XML_TOKEN_NUMBER_OF_POINTS) == 0)
{
gint numberOfPoints;
@@ -1164,6 +1169,7 @@ p_copy_transformed_values(GapMovValues *dstValues, GapMovValues *srcValues
gint ii;
dstValues->version = srcValues->version;
+ dstValues->rotate_threshold = srcValues->rotate_threshold;
dstValues->recordedFrameWidth = srcValues->recordedFrameWidth;
dstValues->recordedFrameHeight = srcValues->recordedFrameHeight;
dstValues->recordedObjWidth = srcValues->recordedObjWidth;
@@ -1315,6 +1321,7 @@ gap_mov_xml_par_load(const char *filename, GapMovValues *productiveValues
gError = NULL;
tmpValues = gap_mov_exec_new_GapMovValues();
tmpValues->dst_image_id = productiveValues->dst_image_id;
+ tmpValues->rotate_threshold = productiveValues->rotate_threshold;
userDataPtr = g_new(GapMovXmlUserData, 1);
userDataPtr->pvals = tmpValues;
@@ -1557,6 +1564,7 @@ gap_mov_xml_par_save(char *filename, GapMovValues *pvals)
fprintf(l_fp, " <%s ", GAP_MOVPATH_XML_TOKEN_CONTROLPOINTS);
gap_xml_write_int_value(l_fp, GAP_MOVPATH_XML_TOKEN_CURRENT_POINT, pvals->point_idx);
gap_xml_write_int_value(l_fp, GAP_MOVPATH_XML_TOKEN_NUMBER_OF_POINTS, pvals->point_idx_max +1);
+ gap_xml_write_gdouble_value(l_fp, GAP_MOVPATH_XML_TOKEN_ROTATE_THRESHOLD, pvals->rotate_threshold, 1, 7);
fprintf(l_fp, " >\n");
/* check for conditonal write
diff --git a/gap/gap_pixelrgn.c b/gap/gap_pixelrgn.c
new file mode 100644
index 0000000..2f08ce8
--- /dev/null
+++ b/gap/gap_pixelrgn.c
@@ -0,0 +1,151 @@
+/* gap_pixelrgn.c
+ * extension for libgimp pixel region processing.
+ * by hof (Wolfgang Hofer)
+ * 2011/12/07
+ *
+ * NOTE: This module deals with libgimp internal private structures
+ * and therefore should become part of libgimp in future versions.
+ * TODO: when future libgimp versions provide comparable functionality
+ * use this module as wrapper to call libgimp
+ *
+ */
+/* LIBGIMP - The GIMP Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * gimppixelrgn.c
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "libgimp/gimp.h"
+#include "gap_pixelrgn.h"
+
+typedef struct _GimpPixelRgnHolder GimpPixelRgnHolder;
+typedef struct _GimpPixelRgnIterator GimpPixelRgnIterator;
+
+struct _GimpPixelRgnHolder
+{
+ GimpPixelRgn *pr;
+ guchar *original_data;
+ gint startx;
+ gint starty;
+ gint count;
+};
+
+struct _GimpPixelRgnIterator
+{
+ GSList *pixel_regions;
+ gint region_width;
+ gint region_height;
+ gint portion_width;
+ gint portion_height;
+ gint process_count;
+};
+
+/**
+ * gap_gimp_pixel_rgns_unref:
+ * @pri_ptr: a regions iterator returned by #gimp_pixel_rgns_register,
+ * #gimp_pixel_rgns_register2 or #gimp_pixel_rgns_process.
+ *
+ * This function cleans up by unref tiles that were initialized in a
+ * loop constuct with gimp_pixel_rgns_register and gimp_pixel_rgns_process calls.
+ * It is intended for clean escape from such a loop at early time
+ * e.g. before all tiles are processed.
+ *
+ * Note: leaving such a loop construct with break or return
+ * without clean up keeps tile references alive
+ * and leads to memory leaks.
+ * Usage CODE example:
+ *
+ * for (pr = gimp_pixel_rgns_register (2, &PR1, &PR2);
+ * pr != NULL;
+ * pr = gimp_pixel_rgns_process (pr))
+ * {
+ * ... evaluate pixel data in pixel regions PR1, PR2
+ * ...
+ * if (further evaluation of the remaining tiles is not necessary)
+ * {
+ * // escaping from the loop without the call
+ * // to gap_gimp_pixel_rgns_unref
+ * // leads to memory leaks because of unbalanced tile ref/unref calls.
+ * break;
+ * }
+ * }
+ * if (pr != NULL)
+ * {
+ * gap_gimp_pixel_rgns_unref(pr);
+ * }
+ **/
+void
+gap_gimp_pixel_rgns_unref (gpointer pri_ptr)
+{
+ GimpPixelRgnIterator *pri;
+ GSList *list;
+
+ g_return_val_if_fail (pri_ptr != NULL, NULL);
+
+ pri = (GimpPixelRgnIterator*) pri_ptr;
+ pri->process_count++;
+
+ /* Unref all referenced tiles and increment the offsets */
+
+ for (list = pri->pixel_regions; list; list = list->next)
+ {
+ GimpPixelRgnHolder *prh = list->data;
+
+ if ((prh->pr != NULL) && (prh->pr->process_count != pri->process_count))
+ {
+ /* This eliminates the possibility of incrementing the
+ * same region twice
+ */
+ prh->pr->process_count++;
+
+ /* Unref the last referenced tile if the underlying region
+ * is a tile manager
+ */
+ if (prh->pr->drawable)
+ {
+ GimpTile *tile = gimp_drawable_get_tile2 (prh->pr->drawable,
+ prh->pr->shadow,
+ prh->pr->x,
+ prh->pr->y);
+ gimp_tile_unref (tile, prh->pr->dirty);
+ }
+
+ prh->pr->x += pri->portion_width;
+
+ if ((prh->pr->x - prh->startx) >= pri->region_width)
+ {
+ prh->pr->x = prh->startx;
+ prh->pr->y += pri->portion_height;
+ }
+ }
+ }
+
+
+
+ /* free the pixel regions list */
+ for (list = pri->pixel_regions; list; list = list->next)
+ {
+ g_slice_free (GimpPixelRgnHolder, list->data);
+ }
+
+ g_slist_free (pri->pixel_regions);
+ g_slice_free (GimpPixelRgnIterator, pri);
+
+
+
+}
diff --git a/gap/gap_pixelrgn.h b/gap/gap_pixelrgn.h
new file mode 100644
index 0000000..6e54de7
--- /dev/null
+++ b/gap/gap_pixelrgn.h
@@ -0,0 +1,36 @@
+/* gap_pixelrgn.h
+ * extension for libgimp pixel region processing.
+ * by hof (Wolfgang Hofer)
+ * 2011/12/07
+ *
+ */
+/* LIBGIMP - The GIMP Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * gimppixelrgn.c
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+#ifndef _GAP_PIXELRGN_H
+#define _GAP_PIXELRGN_H
+
+#include "libgimp/gimp.h"
+
+void gap_gimp_pixel_rgns_unref (gpointer pri_ptr);
+
+#endif
+
diff --git a/gap/gap_player_dialog.c b/gap/gap_player_dialog.c
index e42f1b2..25bc2ce 100644
--- a/gap/gap_player_dialog.c
+++ b/gap/gap_player_dialog.c
@@ -112,6 +112,7 @@
#include "gap_audio_extract.h"
#include "gap_audio_extract.h"
#include "gap_drawable_vref_parasite.h"
+#include "gap_detail_tracking_exec.h"
#include "gap-intl.h"
@@ -346,6 +347,7 @@ static guchar * p_fetch_videoframe(GapPlayerMainGlobalParams *gpp
, gint32 *th_height
, gboolean isBackwards
);
+static void p_init_tile_cache(GapPlayerMainGlobalParams *gpp);
static void p_init_video_playback_cache(GapPlayerMainGlobalParams *gpp);
static void p_init_layout_options(GapPlayerMainGlobalParams *gpp);
static guchar * p_fetch_frame_via_cache(GapPlayerMainGlobalParams *gpp
@@ -1247,10 +1249,29 @@ p_mtrace_image_alive(GapPlayerMainGlobalParams *gpp
/* ------------------------------
+ * p_conditional_detail_tracking
+ * ------------------------------
+ * optional call detail tracking.
+ * that logs coordinates of a detail (typically marked via 2 path vector points)
+ * as XML input file for the MovePath feature.
+ */
+static void
+p_conditional_detail_tracking(GapPlayerMainGlobalParams *gpp, gint32 image_id)
+{
+ if(gpp->enableDetailTracking)
+ {
+ gap_track_detail_on_top_layers_lastvals(image_id);
+ /* flush display to force rendering of newly added layers and vectors */
+ gimp_displays_flush ();
+ }
+} /* end p_conditional_detail_tracking */
+
+
+/* ------------------------------
* p_conditional_attach_dvref
* ------------------------------
* check if there is a valid current videoreference.
- * if yes attach the reference as (temporary) videoreferenc parasite
+ * if yes attach the reference as (temporary) videoreference parasite
* to the specified drawable_id
* (that is typically the layer in the mtrace image)
*/
@@ -1355,6 +1376,8 @@ p_mtrace_image( GapPlayerMainGlobalParams *gpp
g_free(l_name);
}
+ p_conditional_detail_tracking(gpp, gpp->mtrace_image_id);
+
gimp_displays_flush();
}
} /* end p_mtrace_image */
@@ -1423,6 +1446,9 @@ p_mtrace_tmpbuf( GapPlayerMainGlobalParams *gpp
gimp_drawable_set_name(dst_layer_id, l_name);
g_free(l_name);
}
+
+ p_conditional_detail_tracking(gpp, gpp->mtrace_image_id);
+
gimp_displays_flush();
}
} /* end p_mtrace_tmpbuf */
@@ -3100,6 +3126,29 @@ p_fetch_videoframe(GapPlayerMainGlobalParams *gpp
/* -----------------------------
+ * p_init_tile_cache
+ * -----------------------------
+ */
+static void
+p_init_tile_cache(GapPlayerMainGlobalParams *gpp)
+{
+ gchar *value_string;
+
+ gpp->cache_ntiles = GAP_PLAYER_MAIN_DEFAULT_CACHE_NTILES;
+
+ value_string = gimp_gimprc_query("video_player_cache_ntiles");
+ if(value_string)
+ {
+ gpp->cache_ntiles = MAX(0, atol(value_string));
+
+ g_free(value_string);
+ }
+
+ gimp_tile_cache_ntiles(gpp->cache_ntiles);
+
+} /* end p_init_tile_cache */
+
+/* -----------------------------
* p_init_video_playback_cache
* -----------------------------
*/
@@ -3152,6 +3201,21 @@ p_init_layout_options(GapPlayerMainGlobalParams *gpp)
g_free(value_string);
}
+
+ value_string = gimp_gimprc_query("video_player_enable_detail_tracking");
+ if(value_string)
+ {
+ if ((*value_string == 'Y') || (*value_string == 'y'))
+ {
+ gpp->enableDetailTracking = TRUE;
+ }
+ else
+ {
+ gpp->enableDetailTracking = FALSE;
+ }
+
+ g_free(value_string);
+ }
} /* end p_init_layout_options */
@@ -5865,6 +5929,68 @@ on_show_positionscale_checkbutton_toggled(GtkToggleButton *togglebutton,
} /* end on_show_positionscale_checkbutton_toggled */
+/* ---------------------------------------------
+ * on_enable_detail_tracking_checkbutton_toggled
+ * ---------------------------------------------
+ */
+static void
+on_enable_detail_tracking_checkbutton_toggled(GtkToggleButton *togglebutton,
+ GapPlayerMainGlobalParams *gpp)
+{
+ if(gpp == NULL)
+ {
+ return;
+ }
+
+ if (togglebutton->active)
+ {
+ gpp->enableDetailTracking = TRUE;
+ }
+ else
+ {
+ gpp->enableDetailTracking = FALSE;
+ }
+
+} /* end on_enable_detail_tracking_checkbutton_toggled */
+
+/* ---------------------------------
+ * on_detail_tracking_button_clicked
+ * ---------------------------------
+ */
+static void
+on_detail_tracking_button_clicked (GtkButton *button,
+ GapPlayerMainGlobalParams *gpp)
+{
+ gboolean dtailTrackingOk;
+ if(gpp == NULL)
+ {
+ return;
+ }
+ p_stop_playback(gpp);
+ if(gpp->gva_lock)
+ {
+ gpp->request_cancel_video_api = TRUE;
+ return;
+ }
+
+ dtailTrackingOk =
+ gap_detail_tracking_dialog_cfg_set_vals(gpp->mtrace_image_id);
+
+ if(dtailTrackingOk)
+ {
+ gpp->enableDetailTracking = TRUE;
+ if(gpp->detail_tracking_checkbutton)
+ {
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (gpp->detail_tracking_checkbutton), TRUE);
+ }
+ }
+
+} /* end on_detail_tracking_button_clicked */
+
+
+
+
/* -----------------------------
* on_close_button_clicked
* -----------------------------
@@ -6986,6 +7112,40 @@ on_cache_size_spinbutton_changed (GtkEditable *editable,
/* -----------------------------------------
+ * on_tile_cache_size_spinbutton_changed
+ * -----------------------------------------
+ */
+static void
+on_tile_cache_size_spinbutton_changed (GtkEditable *editable,
+ GapPlayerMainGlobalParams *gpp)
+{
+ gdouble nsize;
+
+ if(gpp == NULL)
+ {
+ return;
+ }
+ nsize = GTK_ADJUSTMENT(gpp->cache_ntiles_spinbutton_adj)->value;
+
+ if(gap_debug)
+ {
+ printf("on_tile_cache_size_spinbutton_changed: cache_ntiles:%d newvalue:%d\n"
+ , (int) gpp->cache_ntiles
+ , (int) nsize
+ );
+ }
+
+ if(gpp->cache_ntiles != (gulong)nsize)
+ {
+ gpp->cache_ntiles = (gulong)nsize;
+
+ gimp_tile_cache_ntiles(gpp->cache_ntiles);
+ }
+
+} /* end on_tile_cache_size_spinbutton_changed */
+
+
+/* -----------------------------------------
* on_cache_clear_button_clicked
* -----------------------------------------
*/
@@ -7002,13 +7162,12 @@ on_cache_clear_button_clicked (GtkButton *button,
} /* end on_cache_clear_button_clicked */
-
/* -----------------------------------------
- * p_gimprc_save_boolen_option
+ * p_gimprc_save_option
* -----------------------------------------
*/
static void
-p_gimprc_save_boolen_option (const char *option_name, gboolean value)
+p_gimprc_save_boolean_option (const char *option_name, gboolean value)
{
if(value)
{
@@ -7018,8 +7177,7 @@ p_gimprc_save_boolen_option (const char *option_name, gboolean value)
{
gimp_gimprc_set(option_name, "no");
}
-} /* end p_gimprc_save_boolen_option */
-
+} /* end p_gimprc_save_boolean_option */
/* -----------------------------------------
* on_prefs_save_gimprc_button_clicked
@@ -7029,16 +7187,25 @@ static void
on_prefs_save_gimprc_button_clicked (GtkButton *button,
GapPlayerMainGlobalParams *gpp)
{
+ gchar *valueAsString;
+
if(gpp == NULL)
{
return;
}
gap_player_cache_set_gimprc_bytesize(gpp->max_player_cache);
- p_gimprc_save_boolen_option("video_player_show_go_buttons"
+ p_gimprc_save_boolean_option("video_player_show_go_buttons"
,gpp->show_go_buttons);
- p_gimprc_save_boolen_option("video_player_show_position_scale"
+ p_gimprc_save_boolean_option("video_player_show_position_scale"
,gpp->show_position_scale);
+ p_gimprc_save_boolean_option("video_player_enable_detail_tracking"
+ ,gpp->enableDetailTracking);
+
+ valueAsString = g_strdup_printf("%d", gpp->cache_ntiles);
+ gimp_gimprc_set("video_player_cache_ntiles", valueAsString);
+ g_free(valueAsString);
+
} /* end on_prefs_save_gimprc_button_clicked */
@@ -7081,7 +7248,7 @@ p_new_configframe(GapPlayerMainGlobalParams *gpp)
row = 0;
- /* Cahe size label */
+ /* Cache size label */
label = gtk_label_new (_("Cache Size (MB):"));
gtk_widget_show (label);
gtk_table_attach (GTK_TABLE (table1), label, 0, 1, row, row+1,
@@ -7150,6 +7317,36 @@ p_new_configframe(GapPlayerMainGlobalParams *gpp)
gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress_bar), "0.0 MB");
gpp->progress_bar_cache_usage = progress_bar;
+
+ row++;
+
+ /* tile Chache */
+ label = gtk_label_new(_("Tile Cache:"));
+ gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
+ gtk_table_attach(GTK_TABLE(table1), label, 0, 1, row, row + 1, GTK_FILL, GTK_FILL, 0, 0);
+ gtk_widget_show(label);
+
+
+ /* tile cache size spinutton */
+ spinbutton = gimp_spin_button_new (&adj, /* return value */
+ gpp->cache_ntiles, /* initial_val */
+ 0.0, /* umin */
+ 9000.0, /* umax */
+ 1.0, /* sstep */
+ 10.0, /* pagestep */
+ 0.0, /* page_size */
+ 10.0, /* climb_rate */
+ 0 /* digits */
+ );
+ gtk_widget_show (spinbutton);
+ gpp->cache_ntiles_spinbutton_adj = adj;
+ gtk_table_attach(GTK_TABLE(table1), spinbutton, 1, 2, row, row + 1, GTK_FILL, GTK_FILL, 4, 0);
+ gimp_help_set_help_data(spinbutton, _("gimp tile cache for the player process. (in tiles 64x64 pixel)"),NULL);
+ g_signal_connect (G_OBJECT (gpp->cache_ntiles_spinbutton_adj), "value_changed",
+ G_CALLBACK (on_tile_cache_size_spinbutton_changed),
+ gpp);
+
+
row++;
/* Layout Options label */
@@ -7200,6 +7397,46 @@ p_new_configframe(GapPlayerMainGlobalParams *gpp)
row++;
+
+
+
+
+ /* configure Detail Tracking button */
+ button = gtk_button_new_with_label(_("Configure Tracking:"));
+
+ gtk_widget_show (button);
+ gimp_help_set_help_data(button, _("Configure detail tracking options"),NULL);
+ gtk_table_attach(GTK_TABLE(table1), button, 0, 1, row, row + 1,
+ (GtkAttachOptions) GTK_FILL,
+ (GtkAttachOptions) GTK_FILL, 4, 0);
+ g_signal_connect (G_OBJECT (button), "pressed",
+ G_CALLBACK (on_detail_tracking_button_clicked),
+ gpp);
+
+ checkbutton = gtk_check_button_new_with_label (_("Enable Detail Tracking"));
+ gpp->detail_tracking_checkbutton = checkbutton;
+ gtk_widget_show (checkbutton);
+ gtk_table_attach (GTK_TABLE (table1), checkbutton, 1, 3, row, row+1,
+ (GtkAttachOptions) (GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+ gimp_help_set_help_data (checkbutton, _("ON: Enable detail tracking in snapshot image."
+ " Mark coordinates of one (or 2) significant details in the snapshot image"
+ " using the current path with one or 2 points."
+ " Each further snapshot automatically moves the marked points"
+ " to the coordinates of the corresponding details "
+ " and logs the movement as XML parameters for the MovePath feature."
+ " In case 2 points are marked, the rotation is calculated too.\n"
+ "OFF: Disable detail tracking."), NULL);
+ if(gpp->enableDetailTracking)
+ {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (checkbutton), TRUE);
+ }
+ g_signal_connect (G_OBJECT (checkbutton), "toggled",
+ G_CALLBACK (on_enable_detail_tracking_checkbutton_toggled),
+ gpp);
+
+ row++;
+
/* Save Player Preferences label */
label = gtk_label_new (_("Save Preferences:"));
gtk_widget_show (label);
@@ -8512,11 +8749,14 @@ gap_player_dlg_create(GapPlayerMainGlobalParams *gpp)
gpp->shell_initial_width = -1;
gpp->shell_initial_height = -1;
+ gimp_tile_cache_ntiles(gpp->cache_ntiles);
+ p_init_tile_cache(gpp);
p_init_video_playback_cache(gpp);
p_init_layout_options(gpp);
gpp->mtrace_image_id = -1;
gpp->mtrace_mode = GAP_PLAYER_MTRACE_OFF;
+ gpp->detail_tracking_checkbutton = NULL;
gpp->vindex_creation_is_running = FALSE;
gpp->request_cancel_video_api = FALSE;
diff --git a/gap/gap_player_main.c b/gap/gap_player_main.c
index 339b5d6..1ef9809 100644
--- a/gap/gap_player_main.c
+++ b/gap/gap_player_main.c
@@ -223,6 +223,10 @@ static GapPlayerMainGlobalParams global_params =
, NULL /* GtkWidget *audio_enable_checkbutton */
, NULL /* GapDrawableVideoRef *dvref_ptr */
+, FALSE /* gboolean enableDetailTracking */
+, GAP_PLAYER_MAIN_DEFAULT_CACHE_NTILES /* gulong cache_ntiles */
+, NULL /* GtkObject *cache_ntiles_spinbutton_adj */
+, NULL /* GtkWidget *detail_tracking_checkbutton */
};
diff --git a/gap/gap_player_main.h b/gap/gap_player_main.h
index b6bb01e..3514526 100644
--- a/gap/gap_player_main.h
+++ b/gap/gap_player_main.h
@@ -221,7 +221,7 @@ typedef struct GapPlayerMainGlobalParams {
gint32 max_player_cache; /* max bytesize to use for caching frames
* (at pview widget size)
- * a value of 0 turns cahing OFF
+ * a value of 0 turns caching OFF
*/
GapPlayerCacheCompressionType cache_compression;
gdouble cache_jpeg_quality;
@@ -257,9 +257,16 @@ typedef struct GapPlayerMainGlobalParams {
GtkWidget *audio_enable_checkbutton;
GapDrawableVideoRef *dvref_ptr;
+
+ gboolean enableDetailTracking;
+ gulong cache_ntiles; /* gimp tile cache size for the player process */
+ GtkObject *cache_ntiles_spinbutton_adj;
+ GtkWidget *detail_tracking_checkbutton;
} GapPlayerMainGlobalParams;
+#define GAP_PLAYER_MAIN_DEFAULT_CACHE_NTILES 200
+
#define GAP_PLAYER_MAIN_AUSTAT_UNCHECKED -1
#define GAP_PLAYER_MAIN_AUSTAT_NONE 0
#define GAP_PLAYER_MAIN_AUSTAT_SERVER_STARTED 1
diff --git a/libgapbase/gap_base.c b/libgapbase/gap_base.c
index a2ebd63..caaeb0f 100644
--- a/libgapbase/gap_base.c
+++ b/libgapbase/gap_base.c
@@ -299,6 +299,32 @@ gap_base_dup_filename_and_replace_extension_by_underscore(const char *filename)
} /* end gap_base_dup_filename_and_replace_extension_by_underscore */
+
+
+/* --------------------------------
+ * gap_base_gdouble_to_ascii_string
+ * --------------------------------
+ * convert the specified gdouble value to ascci string.
+ * (always use "." as decimalpoint, independent of LOCALE language settings)
+ * return the converted string. (the caller is responsible to g_free this string after usage)
+ */
+gchar *
+gap_base_gdouble_to_ascii_string(gdouble value, gint precision_digits)
+{
+ gchar *retValueAsSting;
+
+ gchar l_dbl_str[G_ASCII_DTOSTR_BUF_SIZE];
+ gchar l_fmt_str[20];
+ gint l_len;
+
+ g_snprintf(l_fmt_str, sizeof(l_fmt_str), "%%.%df", (int)precision_digits);
+ g_ascii_formatd(l_dbl_str, sizeof(l_dbl_str), l_fmt_str, value);
+
+ retValueAsSting = g_strdup_printf("%s", l_dbl_str);
+ return (retValueAsSting);
+} /* end gap_base_gdouble_to_ascii_string */
+
+
/* --------------------------------
* gap_base_fprintf_gdouble
* --------------------------------
@@ -328,6 +354,7 @@ gap_base_fprintf_gdouble(FILE *fp, gdouble value, gint digits, gint precision_di
} /* end gap_base_fprintf_gdouble */
+
/* ============================================================================
* gap_base_sscan_flt_numbers
* ============================================================================
@@ -414,10 +441,48 @@ gap_base_check_tooltips(gboolean *old_state)
/* -----------------------------------------
+ * gap_base_get_gimprc_gdouble_value
+ * -----------------------------------------
+ * get gdouble configuration value for the keyname gimprc_option_name from the gimprc file.
+ * returns the configure value in constraint to the specified range
+ * (between min_value and max_value)
+ * the specified default_value is returned in case the gimprc
+ * has no entry for the specified gimprc_option_name.
+ */
+gdouble
+gap_base_get_gimprc_gdouble_value (const char *gimprc_option_name
+ , gdouble default_value, gdouble min_value, gdouble max_value)
+{
+ char *value_string;
+ gdouble value;
+
+ value = default_value;
+
+ value_string = gimp_gimprc_query(gimprc_option_name);
+ if(value_string)
+ {
+ gchar *endptr;
+ gchar *nptr;
+ gdouble val;
+
+ nptr = value_string;
+ val = g_ascii_strtod(nptr, &endptr);
+ if(nptr != endptr)
+ {
+ value = val;
+ }
+ g_free(value_string);
+ }
+ return (CLAMP(value, min_value, max_value));
+
+} /* end gap_base_get_gimprc_gdouble_value */
+
+
+/* -----------------------------------------
* gap_base_get_gimprc_int_value
* -----------------------------------------
* get integer configuration value for the keyname gimprc_option_name from the gimprc file.
- * returns the configure value in constaint to the specified range
+ * returns the configure value in constraint to the specified range
* (between min_value and max_value)
* the specified default_value is returned in case the gimprc
* has no entry for the specified gimprc_option_name.
@@ -441,7 +506,6 @@ gap_base_get_gimprc_int_value (const char *gimprc_option_name
} /* end p_get_gimprc_int_value */
-
/* -----------------------------------------
* gap_base_get_gimprc_gboolean_value
* -----------------------------------------
diff --git a/libgapbase/gap_base.h b/libgapbase/gap_base.h
index 97e0f3c..66efa31 100644
--- a/libgapbase/gap_base.h
+++ b/libgapbase/gap_base.h
@@ -94,6 +94,18 @@ char *
gap_base_dup_filename_and_replace_extension_by_underscore(const char *filename);
+
+/* --------------------------------
+ * gap_base_gdouble_to_ascii_string
+ * --------------------------------
+ * convert the specified gdouble value to ascci string.
+ * (always use "." as decimalpoint, independent of LOCALE language settings)
+ * return the converted string. (the caller is responsible to g_free this string after usage)
+ */
+gchar *
+gap_base_gdouble_to_ascii_string(gdouble value, gint precision_digits);
+
+
/* --------------------------------
* gap_base_fprintf_gdouble
* --------------------------------
@@ -126,12 +138,24 @@ gap_base_sscan_flt_numbers(gchar *buf
gboolean
gap_base_check_tooltips(gboolean *old_state);
+/* -----------------------------------------
+ * gap_base_get_gimprc_gdouble_value
+ * -----------------------------------------
+ * get gdouble configuration value for the keyname gimprc_option_name from the gimprc file.
+ * returns the configure value in constraint to the specified range
+ * (between min_value and max_value)
+ * the specified default_value is returned in case the gimprc
+ * has no entry for the specified gimprc_option_name.
+ */
+gdouble
+gap_base_get_gimprc_gdouble_value (const char *gimprc_option_name
+ , gdouble default_value, gdouble min_value, gdouble max_value);
/* -----------------------------------------
* gap_base_get_gimprc_int_value
* -----------------------------------------
* get integer configuration value for the keyname gimprc_option_name from the gimprc file.
- * returns the configure value in constaint to the specified range
+ * returns the configure value in constraint to the specified range
* (between min_value and max_value)
* the specified default_value is returned in case the gimprc
* has no entry for the specified gimprc_option_name.
diff --git a/libgapvidapi/gap_vid_api_ffmpeg.c b/libgapvidapi/gap_vid_api_ffmpeg.c
index 9ac10a7..28d6f20 100644
--- a/libgapvidapi/gap_vid_api_ffmpeg.c
+++ b/libgapvidapi/gap_vid_api_ffmpeg.c
@@ -144,6 +144,8 @@ typedef struct t_GVA_ffmpeg
gint32 samples_buffer_size;
guchar *samples_buffer[2]; /* Buffer for decoded samples from samples_base_a until read position */
+ int16_t *av_samples; /* aligned buffer for decoding audio samples (MMX requires aligned data) */
+ int av_samples_size;
gint32 samples_base[2]; /* start offset of the samples_buffers (unit is samples) */
@@ -385,6 +387,8 @@ p_wrapper_ffmpeg_open_read(char *filename, t_GVA_Handle *gvahand)
handle->aud_pkt.data = NULL; /* start with empty packet */
handle->samples_buffer[0] = NULL; /* set later (at 1.st audio packet read) */
handle->samples_buffer[1] = NULL; /* set later (at 1.st audio packet read) */
+ handle->av_samples = NULL; /* set later (at 1.st audio packet read) */
+ handle->av_samples_size = 0;
handle->samples_base[0] = 0;
handle->samples_base[1] = 0;
handle->bytes_filled[0] = 0;
@@ -631,6 +635,13 @@ p_wrapper_ffmpeg_close(t_GVA_Handle *gvahand)
handle = (t_GVA_ffmpeg *)gvahand->decoder_handle;
+ if(handle->av_samples)
+ {
+ av_free(handle->av_samples);
+ handle->av_samples = NULL;
+ handle->av_samples_size = 0;
+ }
+
if(handle->samples_buffer[0])
{
if(gap_debug) printf("p_wrapper_ffmpeg_close: FREE audio samples_buffer\n");
@@ -3249,7 +3260,10 @@ p_wrapper_ffmpeg_get_audio(t_GVA_Handle *gvahand
l_sample_idx = gvahand->current_sample;
}
- if(gap_debug) printf("p_wrapper_ffmpeg_get_audio samples: %d l_sample_idx:%d\n", (int)samples, (int)l_sample_idx);
+ if(gap_debug)
+ {
+ printf("p_wrapper_ffmpeg_get_audio samples: %d l_sample_idx:%d\n", (int)samples, (int)l_sample_idx);
+ }
l_low_sample_idx = 0;
if (handle->samples_base[0] > 0)
@@ -3268,6 +3282,13 @@ p_wrapper_ffmpeg_get_audio(t_GVA_Handle *gvahand
* (is not chached in the sample_buffers any more)
* we have to reset audio read, and restart reading from the begin
*/
+ if(gap_debug)
+ {
+ printf("ffmpeg_get_audio l_sample_idx:%d l_low_sample_idx:%d (calling reopen)\n"
+ , (int)l_sample_idx
+ , (int)l_low_sample_idx
+ );
+ }
p_ffmpeg_aud_reopen_read(handle, gvahand);
}
@@ -3285,6 +3306,11 @@ p_wrapper_ffmpeg_get_audio(t_GVA_Handle *gvahand
, channel
);
+ if(gap_debug)
+ {
+ printf("p_wrapper_ffmpeg_get_audio DONE: l_rc:%d\n", (int)l_rc);
+ }
+
if(l_rc == 0)
{
if(mode_flag != GVA_AMOD_REREAD)
@@ -3794,7 +3820,13 @@ p_ff_open_input(char *filename, t_GVA_Handle *gvahand, t_GVA_ffmpeg* handle, gb
{
case AVMEDIA_TYPE_AUDIO:
gvahand->atracks++; /* count all audiostraems as audiotrack */
- if(gap_debug) printf("\nInput Audio channels: %d\n", acc->channels);
+ if(gap_debug)
+ {
+ printf("\nInput Audio Track @ streamIndex:%d channels: %d\n"
+ , ii
+ , acc->channels
+ );
+ }
if((gvahand->atracks == gvahand->aud_track)
|| (gvahand->atracks == 1))
{
@@ -3813,6 +3845,12 @@ p_ff_open_input(char *filename, t_GVA_Handle *gvahand, t_GVA_ffmpeg* handle, gb
break;
case AVMEDIA_TYPE_VIDEO:
gvahand->vtracks++; /* count all videostraems as videotrack */
+ if(gap_debug)
+ {
+ printf("\nInput Video Track @ streamIndex:%d\n"
+ , ii
+ );
+ }
if((gvahand->vtracks == gvahand->vid_track)
|| (gvahand->vtracks == 1))
{
@@ -4161,7 +4199,10 @@ p_ffmpeg_vid_reopen_read(t_GVA_ffmpeg *handle, t_GVA_Handle *gvahand)
static void
p_ffmpeg_aud_reopen_read(t_GVA_ffmpeg *handle, t_GVA_Handle *gvahand)
{
- if(gap_debug) printf("p_ffmpeg_aud_reopen_read: REOPEN\n");
+ if(gap_debug)
+ {
+ printf("p_ffmpeg_aud_reopen_read: REOPEN\n");
+ }
/* CLOSE the audio codec */
@@ -4297,6 +4338,11 @@ p_pick_channel( t_GVA_ffmpeg *handle
gint32 this_idx;
gint32 prev_idx;
+ if(gap_debug)
+ {
+ printf("p_pick_channel: START channel:%d\n", (int)channel);
+ }
+
l_samples_picked = 0;
bytes_per_sample = 2 * gvahand->audio_cannels;
@@ -4317,7 +4363,10 @@ p_pick_channel( t_GVA_ffmpeg *handle
*/
l_this_samples = (gint32) (handle->samples_read - sample_idx);
- if(gap_debug) printf("p_pick_channel(2): l_this_samples:%d\n", (int)l_this_samples);
+ if(gap_debug)
+ {
+ printf("p_pick_channel(2): l_this_samples:%d\n", (int)l_this_samples);
+ }
}
if(sample_idx >= handle->samples_base[this_idx])
@@ -4483,6 +4532,11 @@ p_read_audio_packets( t_GVA_ffmpeg *handle, t_GVA_Handle *gvahand, gint32 max_sa
{
printf("p_read_audio_packets: before WHILE max_sample_pos: %d\n", (int)max_sample_pos);
printf("p_read_audio_packets: before WHILE samples_read: %d\n", (int)handle->samples_read);
+ printf("samples_buffer[0]: %d samples_buffer[1]:%d output_samples_ptr:%d\n"
+ , (int)handle->samples_buffer[0]
+ , (int)handle->samples_buffer[1]
+ , (int)handle->output_samples_ptr
+ );
}
while(handle->samples_read < max_sample_pos)
@@ -4504,7 +4558,10 @@ p_read_audio_packets( t_GVA_ffmpeg *handle, t_GVA_Handle *gvahand, gint32 max_sa
if(l_pktlen < 0)
{
/* EOF reached */
- if (gap_debug) printf("p_read_audio_packets: EOF reached (or read ERROR)\n");
+ if (gap_debug)
+ {
+ printf("p_read_audio_packets: EOF reached (or read ERROR)\n");
+ }
gvahand->total_aud_samples = handle->samples_read;
gvahand->all_samples_counted = TRUE;
@@ -4513,7 +4570,12 @@ p_read_audio_packets( t_GVA_ffmpeg *handle, t_GVA_Handle *gvahand, gint32 max_sa
break;
}
- /*if (gap_debug) printf("aud_stream:%d pkt.stream_index #%d, pkt.size: %d samples_read:%d\n", handle->aud_stream_index, handle->aud_pkt.stream_index, handle->aud_pkt.size, (int)handle->samples_read);*/
+ if (gap_debug)
+ {
+ printf("aud_stream:%d pkt.stream_index #%d, pkt.size: %d samples_read:%d\n"
+ , handle->aud_stream_index, handle->aud_pkt.stream_index
+ , handle->aud_pkt.size, (int)handle->samples_read);
+ }
/* check if packet belongs to the selected audio stream */
@@ -4525,14 +4587,20 @@ p_read_audio_packets( t_GVA_ffmpeg *handle, t_GVA_Handle *gvahand, gint32 max_sa
continue;
}
- /* if (gap_debug) printf("using Packet\n"); */
+ if (gap_debug)
+ {
+ printf("using Packet stream_index:%d data:%d size:%d\n"
+ ,(int)handle->aud_pkt.stream_index
+ ,(int)handle->aud_pkt.data
+ ,(int)handle->aud_pkt.size
+ );
+ }
/* packet is part of the selected video stream, use that packet */
handle->abuf_ptr = handle->aud_pkt.data;
handle->abuf_len = handle->aud_pkt.size;
}
- /* if (gap_debug) printf("before avcodec_decode_audio2: abuf_ptr:%d abuf_len:%d\n", (int)handle->abuf_ptr, (int)handle->abuf_len); */
/* decode a frame. return -1 if error, otherwise return the number of
* bytes used. If no audio frame could be decompressed, data_size is
@@ -4540,6 +4608,11 @@ p_read_audio_packets( t_GVA_ffmpeg *handle, t_GVA_Handle *gvahand, gint32 max_sa
*/
data_size = handle->samples_buffer_size;
#ifdef GAP_USES_OLD_FFMPEG_0_5
+ if (gap_debug)
+ {
+ printf("before avcodec_decode_audio2: abuf_ptr:%d abuf_len:%d\n"
+ , (int)handle->abuf_ptr, (int)handle->abuf_len);
+ }
l_len = avcodec_decode_audio2(handle->aud_codec_context /* AVCodecContext * */
,(int16_t *)handle->output_samples_ptr
,&data_size
@@ -4547,17 +4620,53 @@ p_read_audio_packets( t_GVA_ffmpeg *handle, t_GVA_Handle *gvahand, gint32 max_sa
,handle->abuf_len
);
#else
+ {
+ data_size = FFMAX(handle->aud_pkt.size * sizeof(int16_t), AVCODEC_MAX_AUDIO_FRAME_SIZE);
+ if (data_size > handle->av_samples_size)
+ {
+ /* force re-allocation of the aligend buffer */
+ if (handle->av_samples != NULL)
+ {
+ av_free(handle->av_samples);
+ handle->av_samples = NULL;
+ }
+
+ }
+ if (handle->av_samples == NULL)
+ {
+ handle->av_samples = av_malloc(data_size);
+ handle->av_samples_size = data_size;
+ }
+ if(gap_debug)
+ {
+ printf("before avcodec_decode_audio3: av_samples:%d data_size:%d\n"
+ , (int)handle->av_samples
+ , (int)data_size
+ );
+ }
l_len = avcodec_decode_audio3(handle->aud_codec_context /* AVCodecContext * */
- ,(int16_t *)handle->output_samples_ptr
+ ,handle->av_samples
,&data_size
,&handle->aud_pkt
);
+ if(data_size > 0)
+ {
+ /* copy the decoded samples from the aligned av_samples buffer
+ * to current position in the samples_buffer of the relevant channel
+ * Note that calling avcodec_decode_audio3 procedure with handle->output_samples_ptr
+ * as destination pointer for the 16bit samples would be faster,
+ * but will crash when ffmpeg is configured
+ * with enabled MMX and this pointer is not aligned.
+ */
+ memcpy(handle->output_samples_ptr, handle->av_samples, data_size);
+ }
+ }
#endif
if (gap_debug)
{
- printf("after avcodec_decode_audio2: l_len:%d data_size:%d samples_read:%d \n"
+ printf("after avcodec_decode_audioX: l_len:%d data_size:%d samples_read:%d \n"
" sample_fmt:%d %s (expect:%d SAMPLE_FMT_S16)\n"
, (int)l_len
, (int)data_size
@@ -4570,7 +4679,7 @@ p_read_audio_packets( t_GVA_ffmpeg *handle, t_GVA_Handle *gvahand, gint32 max_sa
if(l_len < 0)
{
- printf("p_read_audio_packets: avcodec_decode_audio2 returned ERROR)\n"
+ printf("p_read_audio_packets: avcodec_decode_audioX returned ERROR)\n"
"abuf_len:%d AVCODEC_MAX_AUDIO_FRAME_SIZE:%d samples_buffer_size:%d data_size:%d\n"
, (int)handle->abuf_len
, (int)AVCODEC_MAX_AUDIO_FRAME_SIZE
@@ -4675,6 +4784,10 @@ p_read_audio_packets( t_GVA_ffmpeg *handle, t_GVA_Handle *gvahand, gint32 max_sa
} /* end while packet_read and decode audio frame loop */
+ if(gap_debug)
+ {
+ printf("p_read_audio_packets: DONE return code:%d\n", (int)l_rc);
+ }
return(l_rc);
} /* end p_read_audio_packets */
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 1fb3db7..22ce58f 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -5,6 +5,7 @@ gap/gap_arr_dialog.c
gap/gap_audio_extract.c
gap/gap_audio_wav.c
gap/gap_base_ops.c
+gap/gap_blend_fill_main.c
gap/gap_bluebox.c
gap/gap_bluebox_main.c
gap/gap_colormask_dialog.c
@@ -14,6 +15,9 @@ gap/gap_dbbrowser_utils.c
gap/gap_decode_mplayer_main.c
gap/gap_decode_mplayer.c
gap/gap_decode_xanim.c
+gap/gap_detail_align_exec.c
+gap/gap_detail_tracking_main.c
+gap/gap_detail_tracking_exec.c
gap/gap_filter_foreach.c
gap/gap_filter_main.c
gap/gap_fire_pattern.c
diff --git a/vid_enc_ffmpeg/gap_enc_ffmpeg_gui.c b/vid_enc_ffmpeg/gap_enc_ffmpeg_gui.c
index f0e7037..f6d3898 100644
--- a/vid_enc_ffmpeg/gap_enc_ffmpeg_gui.c
+++ b/vid_enc_ffmpeg/gap_enc_ffmpeg_gui.c
@@ -1066,6 +1066,8 @@ p_init_vid_checkbuttons(GapGveFFMpegGlobalParams *gpp)
, gpp->evl.codec_FLAG2_PSY);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gpp->ff_codec_FLAG2_SSIM_checkbutton)
, gpp->evl.codec_FLAG2_SSIM);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gpp->ff_codec_FLAG2_INTRA_REFRESH_checkbutton)
+ , gpp->evl.codec_FLAG2_INTRA_REFRESH);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gpp->ff_partition_X264_PART_I4X4_checkbutton)
@@ -2935,6 +2937,22 @@ p_create_expert_flags2_frame (GapGveFFMpegGlobalParams *gpp)
G_CALLBACK (on_ff_gint32_checkbutton_toggled),
&gpp->evl.codec_FLAG_GMC);
+
+ /* the INTRA_REFRESH checkbutton */
+ checkbutton = gtk_check_button_new_with_label (_("Intra Refresh"));
+ gpp->ff_codec_FLAG2_INTRA_REFRESH_checkbutton = checkbutton;
+ gtk_widget_show (checkbutton);
+ gtk_table_attach (GTK_TABLE (flags_table), checkbutton, 1, 2, flags_row, flags_row+1,
+ (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+ gimp_help_set_help_data (checkbutton, _("Use periodic insertion of intra blocks instead of keyframes."), NULL);
+ g_object_set_data (G_OBJECT (checkbutton), GAP_ENC_FFGUI_GPP, (gpointer)gpp);
+ g_signal_connect (G_OBJECT (checkbutton), "toggled",
+ G_CALLBACK (on_ff_gint32_checkbutton_toggled),
+ &gpp->evl.codec_FLAG2_INTRA_REFRESH);
+
+
+
flags_row++;
/* the input_preserved checkbutton */
diff --git a/vid_enc_ffmpeg/gap_enc_ffmpeg_main.c b/vid_enc_ffmpeg/gap_enc_ffmpeg_main.c
index 857cdd8..68c49f0 100644
--- a/vid_enc_ffmpeg/gap_enc_ffmpeg_main.c
+++ b/vid_enc_ffmpeg/gap_enc_ffmpeg_main.c
@@ -802,8 +802,9 @@ p_base_get_thread_id_as_int()
/* ---------------------------------------
* p_debug_print_dump_AVCodecContext
* ---------------------------------------
- * dump values of all (200 !!) parameters
+ * dump most parameters values
* of the codec context to stdout
+ * (some unused and deprecated parameters are skipped)
*/
static void
p_debug_print_dump_AVCodecContext(AVCodecContext *codecContext)
@@ -1027,6 +1028,28 @@ p_debug_print_dump_AVCodecContext(AVCodecContext *codecContext)
printf("(psy_trellis %f)\n", (float) codecContext->psy_trellis);
printf("(rc_lookahead %d)\n", (int) codecContext->rc_lookahead);
+#ifndef GAP_USES_OLD_FFMPEG_0_6
+ printf("(crf_max %f)\n", (float) codecContext->crf_max);
+ printf("(log_level_offset %d)\n", (int) codecContext->log_level_offset);
+ /* attribute_deprecated enum AVLPCType lpc_type; */
+ /* attribute_deprecated int lpc_passes; */
+ printf("(slices %d)\n", (int) codecContext->slices);
+ /* uint8_t *subtitle_header; */
+ /* int subtitle_header_size; */
+ /* AVPacket *pkt */
+ /* int is_copy; */
+ printf("(thread_type %d)\n", (int) codecContext->thread_type);
+ printf("(active_thread_type %d)\n", (int) codecContext->active_thread_type);
+ printf("(thread_safe_callbacks %d)\n", (int) codecContext->thread_safe_callbacks);
+ printf("(vbv_delay %lld)\n", codecContext->vbv_delay);
+ printf("(audio_service_type %d)\n", (int) codecContext->audio_service_type);
+ /* enum AVSampleFormat request_sample_fmt */
+ /* int64_t pts_correction_num_faulty_pts; */
+ /* int64_t pts_correction_num_faulty_dts; */
+ /* int64_t pts_correction_last_pts; */
+ /* int64_t pts_correction_last_dts; */
+
+#endif
#endif
printf("AVCodecContext Settings END\n");
@@ -1518,6 +1541,8 @@ gap_enc_ffmpeg_main_init_preset_params(GapGveFFMpegValues *epp, gint preset_idx)
epp->codec_FLAG2_MBTREE = 0; /* 0: FALSE */
epp->codec_FLAG2_PSY = 0; /* 0: FALSE */
epp->codec_FLAG2_SSIM = 0; /* 0: FALSE */
+ /* new flags/flags2 of ffmpeg-0.7.11 2012.01.12 */
+ epp->codec_FLAG2_INTRA_REFRESH = 0; /* 0: FALSE */
epp->partition_X264_PART_I4X4 = 0; /* 0: FALSE */
epp->partition_X264_PART_I8X8 = 0; /* 0: FALSE */
@@ -1525,6 +1550,17 @@ gap_enc_ffmpeg_main_init_preset_params(GapGveFFMpegValues *epp, gint preset_idx)
epp->partition_X264_PART_P4X4 = 0; /* 0: FALSE */
epp->partition_X264_PART_B8X8 = 0; /* 0: FALSE */
+
+ /* new parameters of ffmpeg-0.7.11 2012.01.12 */
+ epp->crf_max = 0.0;
+ epp->log_level_offset = 0;
+ epp->slices = 0;
+ epp->thread_type = 3; /* FF_THREAD_FRAME 1 + FF_THREAD_SLICE 2 */
+ epp->active_thread_type = 0;
+ epp->thread_safe_callbacks = 0;
+ epp->vbv_delay = 0;
+ epp->audio_service_type = 0;
+
if (preset_idx >= GAP_GVE_FFMPEG_PRESET_MAX_ELEMENTS)
{
if(gap_debug)
@@ -2379,6 +2415,9 @@ p_init_video_codec(t_ffmpeg_handle *ffh
p_set_flag(epp->codec_FLAG2_MBTREE, &video_enc->flags2, CODEC_FLAG2_MBTREE);
p_set_flag(epp->codec_FLAG2_PSY, &video_enc->flags2, CODEC_FLAG2_PSY);
p_set_flag(epp->codec_FLAG2_SSIM, &video_enc->flags2, CODEC_FLAG2_SSIM);
+#ifndef GAP_USES_OLD_FFMPEG_0_6
+ p_set_flag(epp->codec_FLAG2_INTRA_REFRESH, &video_enc->flags2, CODEC_FLAG2_INTRA_REFRESH);
+#endif
#endif
@@ -2556,6 +2595,16 @@ p_init_video_codec(t_ffmpeg_handle *ffh
video_enc->psy_rd = (float)epp->psy_rd;
video_enc->psy_trellis = (float)epp->psy_trellis;
video_enc->rc_lookahead = (int)epp->rc_lookahead;
+#ifndef GAP_USES_OLD_FFMPEG_0_6
+ video_enc->crf_max = (float)epp->crf_max;
+ video_enc->log_level_offset = (int)epp->log_level_offset;
+ video_enc->slices = (int)epp->slices;
+ video_enc->thread_type = (int)epp->thread_type;
+ video_enc->active_thread_type = (int)epp->active_thread_type;
+ video_enc->thread_safe_callbacks = (int)epp->thread_safe_callbacks;
+ video_enc->vbv_delay = epp->vbv_delay;
+ video_enc->audio_service_type = epp->audio_service_type;
+#endif
#endif
diff --git a/vid_enc_ffmpeg/gap_enc_ffmpeg_main.h b/vid_enc_ffmpeg/gap_enc_ffmpeg_main.h
index 8f8d1f1..099144e 100644
--- a/vid_enc_ffmpeg/gap_enc_ffmpeg_main.h
+++ b/vid_enc_ffmpeg/gap_enc_ffmpeg_main.h
@@ -38,14 +38,18 @@
#include "avformat.h"
#include "avcodec.h"
-/// start ffmpeg 0.5 / 0.6 support
+/// start ffmpeg 0.5 / 0.6 / 0.7 support
#if LIBAVCODEC_VERSION_MAJOR < 52
#define GAP_USES_OLD_FFMPEG_0_5
#endif
+
#if LIBAVCODEC_VERSION_MAJOR == 52
#if LIBAVCODEC_VERSION_MINOR <= 20
#define GAP_USES_OLD_FFMPEG_0_5
#endif
+#if LIBAVCODEC_VERSION_MINOR <= 72
+#define GAP_USES_OLD_FFMPEG_0_6
+#endif
#endif
@@ -403,6 +407,15 @@ typedef struct {
gdouble psy_trellis; // float psy_trellis;
gint32 rc_lookahead; // int rc_lookahead;
+ /* new params ffmpeg-0.7.11 */
+ gdouble crf_max; // float crf_max;
+ gint32 log_level_offset; // int log_level_offset;
+ gint32 slices; // int slices;
+ gint32 thread_type; // int thread_type;
+ gint32 active_thread_type; // int active_thread_type;
+ gint32 thread_safe_callbacks; // int thread_safe_callbacks;
+ gint64 vbv_delay; // uint64_t vbv_delay;
+ gint32 audio_service_type; // enum AVAudioServiceType audio_service_type;
gint32 codec_FLAG_GMC;
@@ -430,6 +443,7 @@ typedef struct {
gint32 codec_FLAG2_MBTREE;
gint32 codec_FLAG2_PSY;
gint32 codec_FLAG2_SSIM;
+ gint32 codec_FLAG2_INTRA_REFRESH;
gint32 partition_X264_PART_I4X4;
gint32 partition_X264_PART_I8X8;
@@ -564,6 +578,7 @@ typedef struct GapGveFFMpegGlobalParams { /* nick: gpp */
GtkWidget *ff_codec_FLAG2_MBTREE_checkbutton;
GtkWidget *ff_codec_FLAG2_PSY_checkbutton;
GtkWidget *ff_codec_FLAG2_SSIM_checkbutton;
+ GtkWidget *ff_codec_FLAG2_INTRA_REFRESH_checkbutton;
GtkWidget *ff_partition_X264_PART_I4X4_checkbutton;
GtkWidget *ff_partition_X264_PART_I8X8_checkbutton;
diff --git a/vid_enc_ffmpeg/gap_enc_ffmpeg_par.c b/vid_enc_ffmpeg/gap_enc_ffmpeg_par.c
index 955fd4d..4f85072 100644
--- a/vid_enc_ffmpeg/gap_enc_ffmpeg_par.c
+++ b/vid_enc_ffmpeg/gap_enc_ffmpeg_par.c
@@ -234,6 +234,16 @@ p_set_master_keywords(GapValKeyList *keylist, GapGveFFMpegValues *epp)
gap_val_set_keyword(keylist, "(psy_trellis ", &epp->psy_trellis, GAP_VAL_GDOUBLE, 0, "# PSY trellis Strength of psychovisual optimization");
gap_val_set_keyword(keylist, "(rc_lookahead ", &epp->rc_lookahead, GAP_VAL_GINT32, 0, "# Number of frames for frametype and ratecontrol lookahead");
+ /* new params (introduced with ffmpeg 0.7.11 2012.01.12) */
+ gap_val_set_keyword(keylist, "(crf_max ", &epp->crf_max, GAP_VAL_GDOUBLE, 0, "# Constant rate factor maximum");
+ gap_val_set_keyword(keylist, "(log_level_offset ", &epp->log_level_offset, GAP_VAL_GINT32, 0, "#");
+ gap_val_set_keyword(keylist, "(slices ", &epp->slices, GAP_VAL_GINT32, 0, "# Indicates number of picture subdivisions");
+ gap_val_set_keyword(keylist, "(thread_type ", &epp->thread_type, GAP_VAL_GINT32, 0, "# Which multithreading methods to use (FF_THREAD_FRAME 1, FF_THREAD_SLICE 2) ");
+ gap_val_set_keyword(keylist, "(active_thread_type ", &epp->active_thread_type, GAP_VAL_GINT32, 0, "# Which multithreading methods are in use by the codec.");
+ gap_val_set_keyword(keylist, "(thread_safe_callbacks ", &epp->thread_safe_callbacks, GAP_VAL_GINT32, 0, "# Set by the client if its custom get_buffer() callback can be called from another thread");
+ gap_val_set_keyword(keylist, "(vbv_delay ", &epp->vbv_delay, GAP_VAL_GINT32, 0, "# VBV delay coded in the last frame (in periods of a 27 MHz clock)");
+ gap_val_set_keyword(keylist, "(audio_service_type ", &epp->audio_service_type, GAP_VAL_GINT32, 0, "# Type of service that the audio stream conveys.");
+
/* codec flags */
p_set_keyword_bool32(keylist, "(bitexact ", &epp->bitexact, "# CODEC_FLAG_BITEXACT (for testing, unused in productive version)");
@@ -292,6 +302,9 @@ p_set_master_keywords(GapValKeyList *keylist, GapGveFFMpegValues *epp)
p_set_keyword_bool32(keylist, "(partp8x8 ", &epp->partition_X264_PART_P4X4, "# X264_PART_P4X4 Analyze p8x4, p4x8, p4x4");
p_set_keyword_bool32(keylist, "(partb8x8 ", &epp->partition_X264_PART_B8X8, "# X264_PART_B8X8 Analyze b16x8, b8x16 and b8x8");
+ /* codec flags new in ffmpeg-0.7.11 */
+ p_set_keyword_bool32(keylist, "(use_bit_intra_refresh ", &epp->codec_FLAG2_INTRA_REFRESH, "# CODEC_FLAG2_INTRA_REFRESH Use periodic insertion of intra blocks instead of keyframes.");
+
} /* end p_set_master_keywords */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]