Credit SGE - new BlendAlgebra and BlendProgram plugins; major enhancement
authorGood Guy <good1.2guy@gmail.com>
Mon, 17 Feb 2025 20:30:21 +0000 (13:30 -0700)
committerGood Guy <good1.2guy@gmail.com>
Mon, 17 Feb 2025 20:30:21 +0000 (13:30 -0700)
45 files changed:
cinelerra-5.1/plugin_defs
cinelerra-5.1/plugins/Makefile
cinelerra-5.1/plugins/blendalgebra/BlendAlgebraCompile.pl [new file with mode: 0755]
cinelerra-5.1/plugins/blendalgebra/BlendAlgebraStart [new file with mode: 0644]
cinelerra-5.1/plugins/blendalgebra/Makefile [new file with mode: 0644]
cinelerra-5.1/plugins/blendalgebra/arith_addition.ba [new file with mode: 0644]
cinelerra-5.1/plugins/blendalgebra/arith_divide.ba [new file with mode: 0644]
cinelerra-5.1/plugins/blendalgebra/arith_multiply.ba [new file with mode: 0644]
cinelerra-5.1/plugins/blendalgebra/arith_replace.ba [new file with mode: 0644]
cinelerra-5.1/plugins/blendalgebra/arith_subtract.ba [new file with mode: 0644]
cinelerra-5.1/plugins/blendalgebra/blendalgebra.C [new file with mode: 0644]
cinelerra-5.1/plugins/blendalgebra/blendalgebra.h [new file with mode: 0644]
cinelerra-5.1/plugins/blendalgebra/graphart_burn.ba [new file with mode: 0644]
cinelerra-5.1/plugins/blendalgebra/graphart_difference.ba [new file with mode: 0644]
cinelerra-5.1/plugins/blendalgebra/graphart_dodge.ba [new file with mode: 0644]
cinelerra-5.1/plugins/blendalgebra/graphart_hardlight.ba [new file with mode: 0644]
cinelerra-5.1/plugins/blendalgebra/graphart_overlay.ba [new file with mode: 0644]
cinelerra-5.1/plugins/blendalgebra/graphart_screen.ba [new file with mode: 0644]
cinelerra-5.1/plugins/blendalgebra/graphart_softlight.ba [new file with mode: 0644]
cinelerra-5.1/plugins/blendalgebra/logical_and.ba [new file with mode: 0644]
cinelerra-5.1/plugins/blendalgebra/logical_darken.ba [new file with mode: 0644]
cinelerra-5.1/plugins/blendalgebra/logical_lighten.ba [new file with mode: 0644]
cinelerra-5.1/plugins/blendalgebra/logical_max.ba [new file with mode: 0644]
cinelerra-5.1/plugins/blendalgebra/logical_min.ba [new file with mode: 0644]
cinelerra-5.1/plugins/blendalgebra/logical_or.ba [new file with mode: 0644]
cinelerra-5.1/plugins/blendalgebra/logical_xor.ba [new file with mode: 0644]
cinelerra-5.1/plugins/blendalgebra/ovl_normal.ba [new file with mode: 0644]
cinelerra-5.1/plugins/blendalgebra/porterduff_dst.ba [new file with mode: 0644]
cinelerra-5.1/plugins/blendalgebra/porterduff_dstatop.ba [new file with mode: 0644]
cinelerra-5.1/plugins/blendalgebra/porterduff_dstin.ba [new file with mode: 0644]
cinelerra-5.1/plugins/blendalgebra/porterduff_dstout.ba [new file with mode: 0644]
cinelerra-5.1/plugins/blendalgebra/porterduff_dstover.ba [new file with mode: 0644]
cinelerra-5.1/plugins/blendalgebra/porterduff_src.ba [new file with mode: 0644]
cinelerra-5.1/plugins/blendalgebra/porterduff_srcatop.ba [new file with mode: 0644]
cinelerra-5.1/plugins/blendalgebra/porterduff_srcin.ba [new file with mode: 0644]
cinelerra-5.1/plugins/blendalgebra/porterduff_srcout.ba [new file with mode: 0644]
cinelerra-5.1/plugins/blendalgebra/porterduff_srcover.ba [new file with mode: 0644]
cinelerra-5.1/plugins/blendalgebra/ydiff.ba [new file with mode: 0644]
cinelerra-5.1/plugins/blendprogram/BlendProgramCompile.pl [new file with mode: 0755]
cinelerra-5.1/plugins/blendprogram/BlendProgramStart [new file with mode: 0644]
cinelerra-5.1/plugins/blendprogram/Makefile [new file with mode: 0644]
cinelerra-5.1/plugins/blendprogram/background.bp [new file with mode: 0644]
cinelerra-5.1/plugins/blendprogram/blendprogram.C [new file with mode: 0644]
cinelerra-5.1/plugins/blendprogram/blendprogram.h [new file with mode: 0644]
cinelerra-5.1/plugins/blendprogram/chromakey.bp [new file with mode: 0644]

index ded3fd7094c59a886b142002c83058f3b4fcff2c..acf23a08b227b76fdd8ca843b8e8db372c273c26 100644 (file)
@@ -26,6 +26,8 @@ video := \
        alpha \
        bandslide \
        bandwipe \
+       blendalgebra \
+       blendprogram \
        bluebanana \
        blur \
        boxblur \
index 89f164662eafa21ad8ac0ae7a5113f33ae59ed63..fd4031508781fa4e3064f362b548b8075ea54ac8 100644 (file)
@@ -31,6 +31,8 @@ DIRS = $(OPENCV_OBJS) \
        audioscope \
        bandslide \
        bandwipe \
+       blendalgebra \
+       blendprogram \
        bluebanana \
        blur \
        boxblur \
@@ -220,7 +222,10 @@ clean:
        rm -rf $(foreach d,$(DIRS),$(d)/$(OBJDIR))
        rm -rf $(PLUGIN_DIR) $(LADSPA_DIR)
 
+# these plugins have something special to install
 install:
+       $(MAKE) -C blendalgebra $@
+       $(MAKE) -C blendprogram $@
 
 # dependencies for parallel build
 aging:         libeffecttv
diff --git a/cinelerra-5.1/plugins/blendalgebra/BlendAlgebraCompile.pl b/cinelerra-5.1/plugins/blendalgebra/BlendAlgebraCompile.pl
new file mode 100755 (executable)
index 0000000..ecc68df
--- /dev/null
@@ -0,0 +1,289 @@
+#!/usr/bin/perl
+
+# Helper script to compile Cinelerra blend algebra functions
+# Calling: BlendAlgebraCompile.pl [options] <function filename>
+# The special option "-API" shows the numeric version of the script itself
+
+# Several important definitions
+
+# BlendAlgebraCompile.pl script API version. Must not be changed !
+$cin_ba_api = 1;
+
+# C compiler executable and options, can be redefined on user's demand
+$cin_compiler = $ENV{'CIN_CC'};
+$cin_compiler = $ENV{'CC'} if $cin_compiler eq '';
+# a likely default compiler
+$cin_compiler = 'gcc' if $cin_compiler eq '';
+# another possible compiler
+#$cin_compiler = 'clang' if $cin_compiler eq '';
+# a fake compiler for debugging the script itself
+#$cin_compiler = 'echo';
+
+# Mandatory compiler options to build dynamically loadable object, don't change
+$cin_compopts = '-fPIC';
+$cin_ldopts = '-shared';
+$cin_ldlibs = '-lm';
+
+# Adjustable compiler options for optimization, debugging, warnings
+$cin_optopts = '-O2';
+$cin_debopts = '-g';
+$cin_warnopts = '-Wall -W -Wno-unused-parameter';
+# more possible compiler options
+#$cin_optopts = '-Ofast -march=native';
+#$cin_debopts = '-ggdb';
+
+# Set these to 1 if want to optimize, debug, or verbose output by default
+$opt_opt  = 1;
+$opt_deb  = 0;
+$opt_verb = 0;
+
+# Text editor executable, can be redefined on user's preferences
+$cin_editor = $ENV{'CIN_EDITOR'};
+# a likely default editor
+$cin_editor = 'emacs' if $cin_editor eq '';
+# another possible editors
+#$cin_editor = 'konsole -e vim' if $cin_editor eq '';
+#$cin_editor = 'xterm -e vi' if $cin_editor eq '';
+#$cin_editor = 'xedit' if $cin_editor eq '';
+
+# Background execution; set it empty to execute in foreground and block
+$cin_bgr = ' &';
+
+# Nothing to change below this line
+
+# Cinelerra installation path
+$cin_dat = $ENV{'CIN_DAT'};
+
+# Cinelerra user's config directory
+$cin_config = $ENV{'CIN_CONFIG'};
+$cin_config = $ENV{'HOME'}.'/.bcast5'
+  if $cin_config eq '' && $ENV{'HOME'} ne '';
+$cin_config = '.' if $cin_config eq '';
+$me_config = "$cin_config/BlendAlgebraCompile.pl";
+
+sub Usage
+{
+  print "\nCinelerraGG blend algebra compiler driver usage\n\n";
+  print "$0 [options] <ba> compile blend algebra function <ba>\n";
+  print "$0 -edit <ba>     open blend function <ba> in editor\n";
+  print "$0 -API           output own API version\n";
+  print "$0 -h, -help, -?  output this help text\n";
+  print "\nCinelerraGG additional blend algebra compiler options\n\n";
+  print "-compile   don't check modification time, compile unconditionally\n";
+  print "-cfile     don't remove intermediate .c file\n";
+  print "-opt       add optimizing options to compiler command line\n";
+  print "-debug     add debugging options to compiler command line\n";
+  print "-warn      add warning options to compiler command line\n";
+  print "-edit      open blend function source in text editor\n";
+  print "-verbose   verbose execution";
+  print " (active by default)" if $opt_verb;
+  print "\n";
+  print "-noapi     don't copy $0 to $ENV{HOME}/.bcast5\n";
+  print "\nCinelerraGG blend algebra compiler default configuration\n\n";
+  print "Compiler command line: $cin_compiler $cin_compopts $cin_ldopts -o <ba>.so <ba> $cin_ldlibs\n";
+  print "Optimizing options: $cin_optopts";
+  print " (active by default)" if $opt_opt;
+  print "\n";
+  print "Debugging options: $cin_debopts";
+  print " (active by default)" if $opt_deb;
+  print "\n";
+  print "Warning options: $cin_warnopts\n";
+  print "Editor command line: $cin_editor <ba>$cin_bgr\n";
+  print "\nRelevant environment variables\n\n";
+  if ($ENV{'CIN_CC'} ne '')
+  {
+    print "CIN_CC=$ENV{CIN_CC}\n";
+  }
+  else
+  {
+    print "\$CIN_CC: currently not set, fallback: $cin_compiler\n";
+  }
+  if ($ENV{'CC'} ne '')
+  {
+    print "CC=$ENV{CC}\n";
+  }
+  else
+  {
+    print "\$CC: currently not set, fallback: $cin_compiler\n";
+  }
+  if ($ENV{'CIN_EDITOR'} ne '')
+  {
+    print "CIN_EDITOR=$ENV{CIN_EDITOR}\n";
+  }
+  else
+  {
+    print "\$CIN_EDITOR: currently not set, fallback: $cin_editor\n";
+  }
+  if ($ENV{'CIN_DAT'} ne '')
+  {
+    print "CIN_DAT=$ENV{CIN_DAT}\n";
+  }
+  else
+  {
+    print "\$CIN_DAT: currently not set\n";
+  }
+  if ($ENV{'CIN_CONFIG'} ne '')
+  {
+    print "CIN_CONFIG=$ENV{CIN_CONFIG}\n";
+  }
+  else
+  {
+    print "\$CIN_CONFIG: currently not set, fallback: $ENV{HOME}/.bcast5\n";
+  }
+  if ($ENV{'CIN_USERLIB'} ne '')
+  {
+    print "CIN_USERLIB=$ENV{CIN_USERLIB}\n";
+  }
+  else
+  {
+    print "\$CIN_USERLIB: currently not set, fallback: $ENV{HOME}/.bcast5lib\n";
+  }
+  exit 0;
+}
+
+$opt_api = $opt_noapi = 0;
+$opt_edit = $opt_compile = $opt_cfile = $opt_warn = 0;
+$ba_src = '';
+
+# Scan options...
+foreach $arg (@ARGV)
+{
+  unless ($arg =~ /^-/)
+  {
+    $ba_src = $arg;                    # this is the source to compile
+    last;
+  }
+  if ($arg eq '-API')                  # print API version
+  {
+    $opt_api = 1;
+  }
+  elsif ($arg eq '-noapi')             # don't copy script to ~/.bcast5
+  {
+    $opt_noapi = 1;
+  }
+  elsif ($arg eq '-edit')              # open in text editor
+  {
+    $opt_edit = 1;
+    push @opts, $arg;
+  }
+  elsif ($arg eq '-compile')           # compile unconditionally
+  {
+    $opt_compile = 1;
+    push @opts, $arg;
+  }
+  elsif ($arg eq '-cfile')             # don't remove .c file
+  {
+    $opt_cfile = 1;
+    push @opts, $arg;
+  }
+  elsif ($arg eq '-opt')               # optimize
+  {
+    $opt_opt = 1;
+    push @opts, $arg;
+  }
+  elsif ($arg eq '-debug')             # debug
+  {
+    $opt_deb = 1;
+    push @opts, $arg;
+  }
+  elsif ($arg eq '-warn')              # warnings
+  {
+    $opt_warn = 1;
+    push @opts, $arg;
+  }
+  elsif ($arg eq '-verbose')           # print executed commands
+  {
+    $opt_verb = 1;
+    push @opts, $arg;
+  }
+  else { Usage(); }                    # unknown option
+}
+
+# A special internal request: output own API version
+if ($opt_api)
+{
+  print "$cin_ba_api\n";
+  exit 0;
+}
+
+# If a system (not user's) script instance is executed, and the API versions
+# of both scripts do not match, then copy the system script to the user's one
+# (making a backup copy of the latter). Then execute it with the same argument.
+if (! $opt_noapi && $0 ne $me_config)
+{
+  $me_api = 0;
+  $me_api = `\"$me_config\" -API` if -x $me_config;
+  #print "BlendAlgebraCompile: user script=$me_config API=$me_api, need $cin_ba_api\n";
+  if ($me_api != $cin_ba_api)
+  {
+    print "BlendAlgebraCompile: copying $0 to $me_config\n";
+    unlink "$me_config.bak" if -f "$me_config.bak";
+    rename "$me_config", "$me_config.bak" if -f $me_config;
+    system "cp \"$0\" \"$me_config\"";
+    system "chmod +x \"$me_config\"";
+  }
+}
+
+# Do nothing if nothing to compile
+if ($ba_src eq '') { Usage(); }
+#print "BlendAlgebraCompile: source=$ba_src\n";
+unless ($opt_edit || -f $ba_src) { Usage(); }
+
+# Redirection to user configurable script copy
+$arg = join ' ', @opts;
+if (! $opt_noapi && $0 ne $me_config)
+{
+  exec "\"$me_config\" $arg \"$ba_src\"" if -x $me_config;
+}
+
+# If a user's script instance is executed, do everything by myself
+print "BlendAlgebraCompile: executing $0 $arg $ba_src\n" if $opt_verb;
+
+# Call text editor
+if ($opt_edit)
+{
+  print "BlendAlgebraCompile: executing $cin_editor $ba_src$cin_bgr\n";
+  system "$cin_editor \"$ba_src\"$cin_bgr";
+  exit 0;
+}
+
+# Check if the function source is newer than the object
+if ($cin_dat ne '') { $ba_start = "$cin_dat/dlfcn/BlendAlgebraStart"; }
+else                { $ba_start = "BlendAlgebraStart"; }
+$mtime_start = -1;
+($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime_start,
+ $ctime, $blksize, $blocks) = stat ($ba_start);
+
+($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime_src,
+ $ctime, $blksize, $blocks) = stat ($ba_src);
+$mtime_src = $mtime_start if $mtime_src < $mtime_start;
+
+$mtime_so = -1;
+($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime_so,
+ $ctime, $blksize, $blocks) = stat ("$ba_src.so") if -f "$ba_src.so";
+
+if (! $opt_compile && $mtime_so > $mtime_src)
+{
+  print "BlendAlgebraCompile: $ba_src shared object up to date, exiting\n"
+    if $opt_verb;
+  exit 0;
+}
+
+# Call the compiler now
+$cin_compopts .= " $cin_optopts"  if $opt_opt;
+$cin_compopts .= " $cin_debopts"  if $opt_deb;
+$cin_compopts .= " $cin_warnopts" if $opt_warn;
+unlink "$ba_src.c" if -f "$ba_src.c";
+unlink "$ba_src.so" if -f "$ba_src.so";
+print "BlendAlgebraCompile: executing cat $ba_start $ba_src > $ba_src.c\n"
+  if $opt_verb;
+system "echo '# 1 \"$ba_start\"' > \"$ba_src.c\"";
+system "cat \"$ba_start\" >> \"$ba_src.c\"";
+system "echo '# 1 \"$ba_src\"' >> \"$ba_src.c\"";
+system "cat \"$ba_src\" >> \"$ba_src.c\"";
+print "BlendAlgebraCompile: executing $cin_compiler $cin_compopts $cin_ldopts -o $ba_src.so $ba_src.c $cin_ldlibs\n";
+system "$cin_compiler $cin_compopts $cin_ldopts -o \"$ba_src.so\" \"$ba_src.c\" $cin_ldlibs";
+unlink "$ba_src.c" if ! $opt_cfile && -f "$ba_src.c";
+
+# And finally return to the caller
+exit 0;
diff --git a/cinelerra-5.1/plugins/blendalgebra/BlendAlgebraStart b/cinelerra-5.1/plugins/blendalgebra/BlendAlgebraStart
new file mode 100644 (file)
index 0000000..251f154
--- /dev/null
@@ -0,0 +1,174 @@
+/***********************************************-*-C-*-**********/
+
+/* Blend algebra header for Cinelerra Blend Algebra plugin */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <math.h>
+#include <values.h>
+#include <limits.h>
+
+/* These six must match enum BC_CModel from guicast/bccmodels.h */
+#define BC_RGB888      9
+#define BC_RGBA8888   10
+#define BC_YUV888     13
+#define BC_YUVA8888   14
+#define BC_RGB_FLOAT  29
+#define BC_RGBA_FLOAT 30
+
+/* These five must match enum from plugins/blendalgebra/blendalgebra.h */
+#define AUTO    0
+#define RGB     1
+#define YUV     2
+#define HSV     3
+#define PROJECT 4
+
+/* Universal math macros taken mostly from guicast/clip.h */
+#ifndef ABS
+#define ABS(x) ((x) >= 0 ? (x) : -(x))
+#endif
+#ifndef SQR
+#define SQR(x) ((x) * (x))
+#endif
+#ifndef MAX
+#define MAX(x,y) ((x) > (y) ? (x) : (y))
+#endif
+#ifndef MIN
+#define MIN(x,y) ((x) < (y) ? (x) : (y))
+#endif
+#define TO_RAD(x) ((x) * 2 * M_PI / 360)
+#define TO_DEG(x) ((x) * 360 / 2 / M_PI)
+
+/* CLIP returns value, CLAMP does assignment */
+#define CLIP(x,y,z) ((x) < (y) ? (y) : ((x) > (z) ? (z) : (x)))
+#define CLAMP(x,y,z) ((x) = ((x) < (y) ? (y) : ((x) > (z) ? (z) : (x))))
+
+/* Macros usable in the BLEND_ALGEBRA_INIT phase */
+
+/* Specification of working color space inside user's function */
+#define COLORSPACE_RGB {*color_work = RGB;}
+#define COLORSPACE_YUV {*color_work = YUV;}
+#define COLORSPACE_HSV {*color_work = HSV;}
+
+/* Minimum no of tracks required by user's function */
+#define REQUIRE_TRACKS(n) {*MIN_tracks = (n);}
+
+/* Claim parallelization support and inquire if parallelism requested */
+#define PARALLEL_SAFE {*parallel_support = 1;}
+#define PARALLEL_REQUEST parallel_request
+
+/* Dimensionality macros, can be used in both INIT and PROC phases */
+
+/* Total number of tracks got to work on */
+#define TOTAL_TRACKS N_tracks
+
+/* 1 == has alpha channel */
+#define HAS_ALPHA HAS_alpha
+
+/* Frame dimensions in pixels */
+#define WIDTH  FRAME_w
+#define HEIGHT FRAME_h
+
+/* Macros usable in the BLEND_ALGEBRA_PROC phase */
+
+/* Handy macros for pixel and key color components */
+#define R(i) ARG_r[i]
+#define G(i) ARG_g[i]
+#define B(i) ARG_b[i]
+#define Y(i) ARG_r[i]
+#define U(i) ARG_g[i]
+#define V(i) ARG_b[i]
+#define H(i) ARG_r[i]
+#define S(i) ARG_g[i]
+#define A(i) ARG_a[i]
+
+#define R_OUT (*OUT_r)
+#define G_OUT (*OUT_g)
+#define B_OUT (*OUT_b)
+#define Y_OUT (*OUT_r)
+#define U_OUT (*OUT_g)
+#define V_OUT (*OUT_b)
+#define H_OUT (*OUT_r)
+#define S_OUT (*OUT_g)
+#define A_OUT (*OUT_a)
+
+#define KEY_R KEY_r
+#define KEY_G KEY_g
+#define KEY_B KEY_b
+#define KEY_Y KEY_r
+#define KEY_U KEY_g
+#define KEY_V KEY_b
+#define KEY_H KEY_r
+#define KEY_S KEY_g
+#define KEY_A KEY_a
+
+/* Macros for pixel coordinates */
+#define PIX_X ARG_x
+#define PIX_Y ARG_y
+
+/* Macros for various arts to clip pixels */
+#define CLIP_RGB(i) {CLAMP (ARG_r[i], 0, 1); \
+                    CLAMP (ARG_g[i], 0, 1); \
+                    CLAMP (ARG_b[i], 0, 1);}
+
+#define CLIP_YUV(i) {CLAMP (ARG_r[i],  0,   1);   \
+                    CLAMP (ARG_g[i], -0.5, 0.5); \
+                    CLAMP (ARG_b[i], -0.5, 0.5);}
+
+#define CLIP_HSV(i) {if (ARG_r[i] < 0 || ARG_r[i] >= 360) \
+                    ARG_r[i] -= floor(ARG_r[i]/360)*360; \
+                    CLAMP (ARG_g[i], 0, 1); CLAMP (ARG_b[i], 0, 1);}
+
+#define CLIP_A(i) {CLAMP (ARG_a[i], 0, 1);}
+
+#define CLIP_RGBA(i) { CLIP_RGB(i) CLIP_A(i) }
+#define CLIP_YUVA(i) { CLIP_YUV(i) CLIP_A(i) }
+#define CLIP_HSVA(i) { CLIP_HSV(i) CLIP_A(i) }
+
+#define CLIP_RGB_ALL {int I_track; \
+       for (I_track=0; I_track<N_tracks; I_track++) CLIP_RGBA(I_track)}
+
+#define CLIP_YUV_ALL {int I_track; \
+       for (I_track=0; I_track<N_tracks; I_track++) CLIP_YUVA(I_track)}
+
+#define CLIP_HSV_ALL {int I_track; \
+       for (I_track=0; I_track<N_tracks; I_track++) CLIP_HSVA(I_track)}
+
+#define CLIP_RGB_OUT {CLAMP (*OUT_r, 0, 1); \
+                     CLAMP (*OUT_g, 0, 1); \
+                     CLAMP (*OUT_b, 0, 1);}
+
+#define CLIP_YUV_OUT {CLAMP (*OUT_r,  0,   1);   \
+                     CLAMP (*OUT_g, -0.5, 0.5); \
+                     CLAMP (*OUT_b, -0.5, 0.5);}
+
+#define CLIP_HSV_OUT {if (*OUT_r < 0 || *OUT_r >= 360) \
+                     *OUT_r -= floor(*OUT_r/360)*360; \
+                     CLAMP (*OUT_g, 0, 1); CLAMP (*OUT_b, 0, 1);}
+
+#define CLIP_A_OUT {CLAMP (*OUT_a, 0, 1);}
+
+#define CLIP_RGBA_OUT { CLIP_RGB_OUT CLIP_A_OUT }
+#define CLIP_YUVA_OUT { CLIP_YUV_OUT CLIP_A_OUT }
+#define CLIP_HSVA_OUT { CLIP_HSV_OUT CLIP_A_OUT }
+
+/* Mandatory separators between different user's function blocks */
+/* Interfaces must match that from plugins/blendalgebra/blendalgebra.h */
+
+#define BLEND_ALGEBRA_INIT                                                  \
+void baInit (int *color_work, int color_proj, int *MIN_tracks, int N_tracks, \
+int *parallel_support, int parallel_request, int FRAME_w, int FRAME_h,       \
+int HAS_alpha) {
+
+#define BLEND_ALGEBRA_PROC }                           \
+void baProc (int N_tracks,                             \
+float *ARG_r, float *ARG_g, float *ARG_b, float *ARG_a,        \
+float  KEY_r, float  KEY_g, float  KEY_b, float  KEY_a,        \
+float *OUT_r, float *OUT_g, float *OUT_b, float *OUT_a, \
+int    ARG_x, int    ARG_y, int  FRAME_w, int  FRAME_h, int HAS_alpha) {
+
+#define BLEND_ALGEBRA_STOP {return;}
+
+#define BLEND_ALGEBRA_END }
diff --git a/cinelerra-5.1/plugins/blendalgebra/Makefile b/cinelerra-5.1/plugins/blendalgebra/Makefile
new file mode 100644 (file)
index 0000000..308b4db
--- /dev/null
@@ -0,0 +1,75 @@
+include ../../plugin_defs
+
+LDFLAGS += -ldl
+
+OBJS = $(OBJDIR)/blendalgebra.o
+
+PLUGIN = blendalgebra
+
+all::
+
+include ../../plugin_config
+
+CC ?= gcc
+
+FRONT = BlendAlgebraStart
+
+HELPER = BlendAlgebraCompile.pl
+
+DLFCN_DIR = $(BINDIR)/dlfcn
+DLFCN_BA_DIR = $(DLFCN_DIR)/ba
+
+BA_OBJS = \
+       ovl_normal.ba.so \
+       arith_addition.ba.so \
+       arith_subtract.ba.so \
+       arith_multiply.ba.so \
+       arith_divide.ba.so \
+       arith_replace.ba.so \
+       porterduff_dst.ba.so \
+       porterduff_dstatop.ba.so \
+       porterduff_dstin.ba.so \
+       porterduff_dstout.ba.so \
+       porterduff_dstover.ba.so \
+       porterduff_src.ba.so \
+       porterduff_srcatop.ba.so \
+       porterduff_srcin.ba.so \
+       porterduff_srcout.ba.so \
+       porterduff_srcover.ba.so \
+       logical_min.ba.so \
+       logical_max.ba.so \
+       logical_lighten.ba.so \
+       logical_darken.ba.so \
+       logical_and.ba.so \
+       logical_or.ba.so \
+       logical_xor.ba.so \
+       graphart_overlay.ba.so \
+       graphart_screen.ba.so \
+       graphart_burn.ba.so \
+       graphart_dodge.ba.so \
+       graphart_difference.ba.so \
+       graphart_hardlight.ba.so \
+       graphart_softlight.ba.so \
+       ydiff.ba.so
+
+BA_SRCS = $(BA_OBJS:.ba.so=.ba)
+
+$(OBJDIR)/blendalgebra.o: blendalgebra.C blendalgebra.h
+
+%.ba.so: %.ba $(FRONT) $(HELPER)
+       rm -f $@
+       CIN_CC=$(CC) CIN_DAT= ./$(HELPER) -noapi -compile -opt -warn $<
+
+all:: $(OUTPUT) $(BA_OBJS)
+
+install:: $(BA_OBJS)
+
+install::
+       mkdir -p $(DLFCN_DIR)
+       mkdir -p $(DLFCN_BA_DIR)
+       cp -a $(FRONT) $(HELPER) $(DLFCN_DIR)
+       chmod +x $(DLFCN_DIR)/$(HELPER)
+       cp -a $(BA_SRCS) $(BA_OBJS) $(DLFCN_BA_DIR)
+
+clean::
+       rm -f  $(BA_OBJS) *~
diff --git a/cinelerra-5.1/plugins/blendalgebra/arith_addition.ba b/cinelerra-5.1/plugins/blendalgebra/arith_addition.ba
new file mode 100644 (file)
index 0000000..e2bfa20
--- /dev/null
@@ -0,0 +1,25 @@
+/***********************************************-*-C-*-**********/
+/* Overlay mode: Arithmetic Addition, for any number of tracks */
+
+BLEND_ALGEBRA_INIT
+
+COLORSPACE_RGB
+PARALLEL_SAFE
+
+BLEND_ALGEBRA_PROC
+
+int i;
+
+R_OUT = G_OUT = B_OUT = A_OUT = 0;
+
+for (i=0; i<TOTAL_TRACKS; i++)
+{
+  R_OUT += R(i);
+  G_OUT += G(i);
+  B_OUT += B(i);
+  A_OUT += A(i);
+}
+
+if (! HAS_ALPHA) A_OUT = 1;
+
+BLEND_ALGEBRA_END
diff --git a/cinelerra-5.1/plugins/blendalgebra/arith_divide.ba b/cinelerra-5.1/plugins/blendalgebra/arith_divide.ba
new file mode 100644 (file)
index 0000000..5f51911
--- /dev/null
@@ -0,0 +1,25 @@
+/***********************************************-*-C-*-**********/
+/* Overlay mode: Arithmetic Divide */
+
+BLEND_ALGEBRA_INIT
+
+COLORSPACE_RGB
+PARALLEL_SAFE
+REQUIRE_TRACKS(2)
+
+BLEND_ALGEBRA_PROC
+
+#define s 1
+#define d 0
+
+R_OUT = R(s) * (1-A(d)) + R(d) * (1-A(s));
+G_OUT = G(s) * (1-A(d)) + G(d) * (1-A(s));
+B_OUT = B(s) * (1-A(d)) + B(d) * (1-A(s));
+
+if (R(d) != 0) R_OUT += R(s) / R(d);
+if (G(d) != 0) G_OUT += G(s) / G(d);
+if (B(d) != 0) B_OUT += B(s) / B(d);
+
+A_OUT = A(s) + A(d) - A(s) * A(d);
+  
+BLEND_ALGEBRA_END
diff --git a/cinelerra-5.1/plugins/blendalgebra/arith_multiply.ba b/cinelerra-5.1/plugins/blendalgebra/arith_multiply.ba
new file mode 100644 (file)
index 0000000..a43738c
--- /dev/null
@@ -0,0 +1,20 @@
+/***********************************************-*-C-*-**********/
+/* Overlay mode: Arithmetic Multiply */
+
+BLEND_ALGEBRA_INIT
+
+COLORSPACE_RGB
+PARALLEL_SAFE
+REQUIRE_TRACKS(2)
+
+BLEND_ALGEBRA_PROC
+
+#define s 1
+#define d 0
+
+R_OUT = R(s) * (1-A(d)) + R(d) * (1-A(s)) + R(s) * R(d);
+G_OUT = G(s) * (1-A(d)) + G(d) * (1-A(s)) + G(s) * G(d);
+B_OUT = B(s) * (1-A(d)) + B(d) * (1-A(s)) + B(s) * B(d);
+A_OUT = A(s) + A(d) - A(s) * A(d);
+  
+BLEND_ALGEBRA_END
diff --git a/cinelerra-5.1/plugins/blendalgebra/arith_replace.ba b/cinelerra-5.1/plugins/blendalgebra/arith_replace.ba
new file mode 100644 (file)
index 0000000..b483d26
--- /dev/null
@@ -0,0 +1,17 @@
+/***********************************************-*-C-*-**********/
+/* Overlay mode: Arithmetic Relpace */
+
+BLEND_ALGEBRA_INIT
+
+COLORSPACE_RGB
+PARALLEL_SAFE
+REQUIRE_TRACKS(2)
+
+BLEND_ALGEBRA_PROC
+
+R_OUT = R(1);
+G_OUT = G(1);
+B_OUT = B(1);
+A_OUT = A(1);
+  
+BLEND_ALGEBRA_END
diff --git a/cinelerra-5.1/plugins/blendalgebra/arith_subtract.ba b/cinelerra-5.1/plugins/blendalgebra/arith_subtract.ba
new file mode 100644 (file)
index 0000000..b282787
--- /dev/null
@@ -0,0 +1,22 @@
+/***********************************************-*-C-*-**********/
+/* Overlay mode: Arithmetic Subtract */
+
+BLEND_ALGEBRA_INIT
+
+COLORSPACE_RGB
+PARALLEL_SAFE
+REQUIRE_TRACKS(2)
+
+BLEND_ALGEBRA_PROC
+
+#define s 1
+#define d 0
+
+R_OUT = R(s) - R(d);
+G_OUT = G(s) - G(d);
+B_OUT = B(s) - B(d);
+
+if (HAS_ALPHA) A_OUT = A(s) - A(d);
+else A_OUT = 1;
+
+BLEND_ALGEBRA_END
diff --git a/cinelerra-5.1/plugins/blendalgebra/blendalgebra.C b/cinelerra-5.1/plugins/blendalgebra/blendalgebra.C
new file mode 100644 (file)
index 0000000..531a755
--- /dev/null
@@ -0,0 +1,2297 @@
+/*
+ * CINELERRA
+ * Copyright (C) 2008 Adam Williams <broadcast at earthling dot net>
+ *
+ * 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
+ *
+ */
+
+#include "filexml.h"
+#include "keyframe.h"
+#include "language.h"
+#include "mainerror.h"
+#include "clip.h"
+#include "bccolors.h"
+#include "loadbalance.h"
+#include "filesystem.h"
+#include "vframe.h"
+#include "mainsession.h"
+#include "mwindow.h"
+#include "pluginserver.h"
+#include "blendalgebra.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <time.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <dlfcn.h>
+
+//#define DEBUG
+
+// Sorry for this global variable, it is needed to propagate the signal
+// from the GUI instance to the processing instance of the plugin
+// that some user function might have changed and need recompilation
+static time_t BlendAlgebraTstamp = -1;
+
+REGISTER_PLUGIN(BlendAlgebra)
+
+LOAD_CONFIGURATION_MACRO(BlendAlgebra, BlendAlgebraConfig)
+
+NEW_WINDOW_MACRO(BlendAlgebra, BlendAlgebraWindow)
+
+const char *BlendAlgebra::plugin_title() { return N_("Blend Algebra"); }
+
+int BlendAlgebra::is_realtime()     { return 1; }
+int BlendAlgebra::is_multichannel() { return 1; }
+int BlendAlgebra::is_synthesis()    { return 1; }
+
+////////////////////////////////////////////
+// Plugin configuration class implementation
+////////////////////////////////////////////
+
+BlendAlgebraConfig::BlendAlgebraConfig()
+{
+  funcname[0]  = 0;                            // no function per default
+  parallel     = 1;                            // parallelize per default
+  clipcolors   = 1;                            // clip colors per default
+  clear_input  = 1;                            // like Overlay plugin does
+  direction    = BlendAlgebraConfig::BOTTOM_FIRST; // as in Overlay plugin
+  output_track = BlendAlgebraConfig::TOP;      // as in Overlay plugin
+  colorspace   = BlendAlgebraConfig::AUTO;     // requested from function
+  red = green = blue = 0;                      // black key color per default
+  alpha = 0;                                   // transparent per default
+}
+
+int BlendAlgebraConfig::equivalent(BlendAlgebraConfig &that)
+{
+  return
+    !strcmp (funcname, that.funcname) &&
+    parallel    == that.parallel      &&
+    clipcolors  == that.clipcolors    &&
+    clear_input == that.clear_input   &&
+    direction   == that.direction     &&
+    colorspace  == that.colorspace    &&
+    EQUIV (red,   that.red)           &&
+    EQUIV (green, that.green)         &&
+    EQUIV (blue,  that.blue)          &&
+    EQUIV (alpha, that.alpha);
+}
+
+void BlendAlgebraConfig::copy_from(BlendAlgebraConfig &that)
+{
+  strcpy (funcname, that.funcname);
+  parallel    = that.parallel;
+  clipcolors  = that.clipcolors;
+  clear_input = that.clear_input;
+  direction   = that.direction;
+  colorspace  = that.colorspace;
+  red         = that.red;
+  green       = that.green;
+  blue        = that.blue;
+  alpha       = that.alpha;
+}
+
+void BlendAlgebraConfig::interpolate (BlendAlgebraConfig &prev,
+                                     BlendAlgebraConfig &next,
+                                     int64_t prev_frame,
+                                     int64_t next_frame,
+                                     int64_t current_frame)
+{
+  double next_scale =
+    (double) (current_frame - prev_frame) / (next_frame - prev_frame);
+  double prev_scale =
+    (double) (next_frame - current_frame) / (next_frame - prev_frame);
+
+  red   = prev.red   * prev_scale + next.red   * next_scale;
+  green = prev.green * prev_scale + next.green * next_scale;
+  blue  = prev.blue  * prev_scale + next.blue  * next_scale;
+  alpha = prev.alpha * prev_scale + next.alpha * next_scale;
+
+  strcpy (funcname, prev.funcname);
+  parallel    = prev.parallel;
+  clipcolors  = prev.clipcolors;
+  clear_input = prev.clear_input;
+  direction   = prev.direction;
+  colorspace  = prev.colorspace;
+}
+
+const char *BlendAlgebraConfig::direction_to_text(int direction)
+{
+  switch(direction)
+  {
+  case BlendAlgebraConfig::BOTTOM_FIRST: return _("Bottom first");
+  case BlendAlgebraConfig::TOP_FIRST:    return _("Top first");
+  }
+  return "";
+}
+
+const char *BlendAlgebraConfig::output_to_text(int output_track)
+{
+  switch(output_track)
+  {
+  case BlendAlgebraConfig::TOP:    return _("Top");
+  case BlendAlgebraConfig::BOTTOM: return _("Bottom");
+  }
+  return "";
+}
+
+const char *BlendAlgebraConfig::colorspace_to_text(int colorspace)
+{
+  switch(colorspace)
+  {
+  case BlendAlgebraConfig::AUTO:    return _("auto");
+  case BlendAlgebraConfig::RGB:     return _("RGB");
+  case BlendAlgebraConfig::YUV:     return _("YUV");
+  case BlendAlgebraConfig::HSV:     return _("HSV");
+  case BlendAlgebraConfig::PROJECT: return _("of project");
+  }
+  return "";
+}
+
+int BlendAlgebraConfig::get_key_color()
+{
+  int red   = (int) (CLIP (this->red,   0, 1) * 255);
+  int green = (int) (CLIP (this->green, 0, 1) * 255);
+  int blue  = (int) (CLIP (this->blue,  0, 1) * 255);
+  return (red << 16) | (green << 8) | blue;
+}
+
+////////////////////////////////////////////
+// Plugin dialog window class implementation
+////////////////////////////////////////////
+
+BlendAlgebraFuncname::BlendAlgebraFuncname(BlendAlgebra *plugin,
+                                          const char *funcname,
+                                          BlendAlgebraWindow *gui,
+                                          int x, int y)
+  : BC_TextBox(x, y, gui->get_w()-x-xS(10), 1, funcname)
+{
+  this->plugin = plugin;
+  this->gui = gui;
+}
+
+int BlendAlgebraFuncname::handle_event()
+{
+  // Perhaps locking is not needed here
+  // as GUI is driven by a separate plugin instance
+  plugin->func_lock->lock("BlendAlgebraFuncname::handle_event");
+  strncpy(plugin->config.funcname, get_text(),
+         sizeof(plugin->config.funcname)-1);
+  BlendAlgebraTstamp = time(NULL);     // time of possible function change
+#ifdef DEBUG
+  printf ("BlendAlgebraFuncname::handle_event setting function %s\n   timestamp %s",
+         plugin->config.funcname, ctime(&BlendAlgebraTstamp));
+#endif
+  plugin->func_lock->unlock();
+  plugin->send_configure_change();
+  return 1;
+}
+
+BlendAlgebraDetach::BlendAlgebraDetach (BlendAlgebra *plugin,
+                                       BlendAlgebraWindow *gui,
+                                       int x, int y)
+  : BC_GenericButton (x, y, _("Detach"))
+{
+  this->plugin = plugin;
+  this->gui = gui;
+}
+
+int BlendAlgebraDetach::handle_event()
+{
+  if (! plugin->config.funcname[0]) return 1;// already detached, nothing to do
+
+  plugin->func_lock->lock("BlendAlgebraDetach::handle_event");
+  plugin->config.funcname[0] = 0;      // clear function, inducing detach
+  BlendAlgebraTstamp = time(NULL);     // force refresh of dlopen'd functions
+#ifdef DEBUG
+  printf ("BlendAlgebraDetach::handle_event clearing function\n   timestamp %s",
+         ctime(&BlendAlgebraTstamp));
+#endif
+  plugin->func_lock->unlock();
+
+  gui->lock_window("BlendAlgebraDetach::handle_event");
+  gui->funcname->update(plugin->config.funcname);
+  gui->unlock_window();
+
+  plugin->send_configure_change();
+  return 1;
+}
+
+BlendAlgebraRefresh::BlendAlgebraRefresh (BlendAlgebra *plugin,
+                                         BlendAlgebraWindow *gui,
+                                         int x, int y)
+  : BC_GenericButton (x, y, _("Refresh"))
+{
+  this->plugin = plugin;
+  this->gui = gui;
+}
+
+int BlendAlgebraRefresh::handle_event()
+{
+  plugin->func_lock->lock("BlendAlgebraRefresh::handle_event");
+  BlendAlgebraTstamp = time(NULL);     // force refresh of dlopen'd functions
+#ifdef DEBUG
+  printf ("BlendAlgebraRefresh::handle_event timestamp %s",
+         ctime(&BlendAlgebraTstamp));
+#endif
+  plugin->func_lock->unlock();
+  return 1;    // just reattach all functions, without reconfiguration
+}
+
+BlendAlgebraEdit::BlendAlgebraEdit (BlendAlgebra *plugin,
+                                   BlendAlgebraWindow *gui,
+                                   int x, int y)
+  : BC_GenericButton (x, y, _("Edit..."))
+{
+  this->plugin = plugin;
+  this->gui = gui;
+}
+
+int BlendAlgebraEdit::handle_event()
+{
+  char fname[BCTEXTLEN], dir[BCTEXTLEN], str[2*BCTEXTLEN];
+
+  strcpy (fname, plugin->config.funcname);
+  if (! fname[0])
+  {
+    eprintf (_("Blend Algebra: no source file to edit, select function first\n"));
+    return 1;
+  }
+
+  // Evtl make function path absolute by prepending project path to it
+  if (fname[0] != '/') // fname is relative, prepend current project path
+  {
+    strcpy (dir, plugin->server->mwindow->session->filename);
+    if (dir[0])
+    {
+      char *cp = strrchr (dir, '/');
+      if (cp)
+      {
+       cp[1] = 0;              // strip project filename off from project path
+       strcat (dir, fname);    // concatenate obtained path with function name
+       strcpy (fname, dir);
+      }
+    }
+  }
+
+  // This will run configured external editor via perl script
+  // If editor start is not backgrounded, GUI will block until editor exits
+  sprintf(str, "\"%s/dlfcn/BlendAlgebraCompile.pl\" -edit \"%s\"",
+         getenv("CIN_DAT"), fname);
+#ifdef DEBUG
+  printf ("BlendAlgebraEdit::handle_event: executing:\n   %s\n", str);
+#endif
+  system (str);                                // runs configured external editor
+
+  plugin->func_lock->lock("BlendAlgebraEdit::handle_event");
+  BlendAlgebraTstamp = time(NULL);     // force refresh of dlopen'd functions
+#ifdef DEBUG
+  printf ("BlendAlgebraEdit::handle_event edited function %s\n   timestamp %s",
+         fname, ctime(&BlendAlgebraTstamp));
+#endif
+  plugin->func_lock->unlock();
+
+  // Evtl functions will be recompiled, but no configure change
+  return 1;
+}
+
+BlendAlgebraFileButton::BlendAlgebraFileButton(BlendAlgebra *plugin,
+                                              BlendAlgebraWindow *gui,
+                                              int x, int y)
+  : BC_GenericButton(x, y, _("Attach..."))
+{
+  this->plugin = plugin;
+  this->gui = gui;
+  this->file_box = 0;
+}
+
+BlendAlgebraFileButton::~BlendAlgebraFileButton()
+{
+  stop();
+}
+
+int BlendAlgebraFileButton::handle_event()
+{
+  gui->editing_lock->lock();
+
+  if (! gui->editing)
+  {
+    gui->editing = 1;
+    gui->editing_lock->unlock();
+    start();
+  }
+  else
+  {
+    flicker();
+    gui->editing_lock->unlock();
+  }
+
+  return 1;
+}
+
+void BlendAlgebraFileButton::run()
+{
+  int result = 1;
+  const char *fpath;
+  char fname[BCTEXTLEN], dir[BCTEXTLEN];
+
+  strcpy (fname, plugin->config.funcname);
+
+  // This infinite loop is exited after clicking OK or Cancel in FileBox.
+  // There are several special buttons which replace the FileBox initial path
+  // with another predefined path and close FileBox with reinit_path flag set.
+  // If reinit_path is set, the loop is repeated with that extracted path.
+  // reinit_path is cleared inside BlendAlgebraFileBox constructor.
+
+  for (;;)             // will exit when reinit_path == 0
+  {    // Evtl make function path absolute by prepending project path to it
+    if (fname[0] != '/') // fname is relative, prepend current project path
+    {
+      strcpy (dir, plugin->server->mwindow->session->filename);
+      if (dir[0])
+      {
+       char *cp = strrchr (dir, '/');
+       if (cp)
+       {
+         cp[1] = 0;            // strip project filename off from project path
+         strcat (dir, fname);  // concatenate obtained path with function name
+         strcpy (fname, dir);
+       }
+      }
+    }
+#ifdef DEBUG
+    printf ("BlendAlgebraFileButton::run creating file_box (%s)\n", fname);
+#endif
+    file_box = new BlendAlgebraFileBox (plugin, gui, fname);
+    file_box->update_history();        // otherwise actual dir can be forgotten
+    file_box->create_objects();
+    file_box->lock_window ("BlendAlgebraFileButton::run");
+    file_box->add_objects();                   // add our special buttons
+    file_box->update_filter ("*.ba");
+    file_box->unlock_window();
+    result = file_box->run_window();
+    if (file_box->reinit_path)                 // if set, a button was clicked
+    {
+      fpath = file_box->get_current_path();    // current, as set by buttons
+#ifdef DEBUG
+      printf ("BlendAlgebraFileButton::run file_box returned %d reinit_path=%d\n   fpath=%s\n",
+             result, file_box->reinit_path, fpath);
+#endif
+      strncpy (fname, fpath ? fpath : "", sizeof(fname)-1);
+      delete file_box;
+      file_box = 0;
+      continue;        // reinit_path will be cleared on repeat in FileBox constructor
+    }
+    fpath = file_box->get_submitted_path();    // submitted, as set by user
+#ifdef DEBUG
+    printf ("BlendAlgebraFileButton::run file_box returned %d reinit_path=%d\n   fpath=%s\n",
+           result, file_box->reinit_path, fpath);
+#endif
+    strncpy (fname, fpath ? fpath : "", sizeof(fname)-1);
+    delete file_box;
+    file_box = 0;
+    break;             // reinit_path remains cleared, exit loop
+  }                    // until reinit_path == 0
+
+  gui->editing_lock->lock();
+  if (result) gui->editing = 0;
+  gui->editing_lock->unlock();
+  if (! gui->editing) return;                          // Cancel pressed
+
+  if (fname[0])                // selected function name not empty, canonicalize it
+  {    // if function is under project's dir, strip dir and make path relative
+    strcpy (dir, plugin->server->mwindow->session->filename);
+    // another project location might be plugin->server->mwindow->edl->path
+    if (dir[0])                // project filename contains some path
+    {
+      char *cp = strrchr (dir, '/');
+      if (cp)
+      {
+       cp[1] = 0;      // the directory of current project with trailing slash
+       if (! strncmp (fname, dir, strlen(dir)))
+       {
+         strcpy (dir, fname+strlen(dir));      // strip project dir off
+         strcpy (fname, dir+strspn(dir,"/"));  // ensure path is relative
+       }
+      }
+    }
+    if (strlen (fname) < 3 || strcmp (fname+strlen(fname)-3, ".ba"))
+      strcat (fname, ".ba");   // suggest '.ba' suffix for blend functions
+  }
+
+  // Actualize selected function in config and in the main plugin dialog
+  plugin->func_lock->lock("BlendAlgebraFileButton::run");
+  strcpy (plugin->config.funcname, fname);
+  BlendAlgebraTstamp = time(NULL);     // time of possible function change
+#ifdef DEBUG
+  printf ("BlendAlgebraFileButton::run setting function %s\n   timestamp %s",
+         plugin->config.funcname, ctime(&BlendAlgebraTstamp));
+#endif
+  plugin->func_lock->unlock();
+  gui->lock_window("BlendAlgebraFileButton::run");
+  gui->funcname->update(plugin->config.funcname);
+  gui->unlock_window();
+  gui->editing_lock->lock();
+  gui->editing = 0;
+  gui->editing_lock->unlock();
+
+  plugin->send_configure_change();
+}
+
+void BlendAlgebraFileButton::stop()
+{
+  if (file_box) file_box->set_done(1);
+  join();
+}
+
+BlendAlgebraFileBox::BlendAlgebraFileBox(BlendAlgebra *plugin,
+                                        BlendAlgebraWindow *gui,
+                                        char *init_path)
+  : BC_FileBox(0, BC_WindowBase::get_resources()->filebox_h/2, init_path,
+              _("Blend Algebra: Select function source file"),"")
+{
+  this->plugin = plugin;
+  this->gui = gui;
+
+  to_curdir   = 0;
+  to_usrlib   = 0;
+  to_syslib   = 0;
+  copy_curdir = 0;
+  copy_usrlib = 0;
+  file_edit   = 0;
+  reinit_path = 0;
+}
+
+BlendAlgebraFileBox::~BlendAlgebraFileBox()
+{
+}
+
+// We need several additional buttons not foreseen in the bare FileBox.
+// We arrange them in the place of (empty) FileBox caption.
+void BlendAlgebraFileBox::add_objects()
+{
+  int xs10 = xS(10), xs5 = xS(5);
+  int ys10 = yS(10);
+  int x = xs10, y = ys10, x2;
+
+  add_subwindow(to_curdir = new BlendAlgebraToCurdir(this, x, y));
+  x2 = x+to_curdir->get_w()+xs5;
+  add_subwindow(to_usrlib = new BlendAlgebraToUsrlib(this, x2, y));
+  x2 += to_usrlib->get_w()+xs5;
+  add_subwindow(to_syslib = new BlendAlgebraToSyslib(this, x2, y));
+  y = get_y_margin();
+  add_subwindow(copy_curdir = new BlendAlgebraCopyCurdir(this, x, y));
+  x2 = x+copy_curdir->get_w()+xs5;
+  add_subwindow(copy_usrlib = new BlendAlgebraCopyUsrlib(this, x2, y));
+  x2 += copy_usrlib->get_w()+xs5;
+  add_subwindow(file_edit = new BlendAlgebraFileEdit(this, x2, y));
+  flush();
+}
+
+int BlendAlgebraFileBox::resize_event(int w, int h)
+{
+  int xs10 = xS(10), xs5 = xS(5);
+  int x = xs10, y, x2;
+
+  BC_FileBox::resize_event (w, h);
+
+  y = get_y_margin();
+  copy_curdir->reposition_window (x, y);
+  x2 = x+copy_curdir->get_w()+xs5;
+  copy_usrlib->reposition_window (x2, y);
+  x2 += copy_usrlib->get_w()+xs5;
+  file_edit->reposition_window (x2, y);
+
+  flush();
+  return 1;
+}
+
+BlendAlgebraToCurdir::BlendAlgebraToCurdir(BlendAlgebraFileBox *file_box,
+                                          int x, int y)
+  : BC_GenericButton (x, y, _("=>Project"))
+{
+  this->file_box = file_box;
+}
+
+int BlendAlgebraToCurdir::handle_event()
+{
+  char *cp, fname[BCTEXTLEN], dir[BCTEXTLEN], path[BCTEXTLEN];
+
+  strcpy (dir, file_box->plugin->server->mwindow->session->filename);
+  if (dir[0])                  // first get current project directory
+  {
+    cp = strrchr (dir, '/');
+    if (cp) *cp = 0;
+    else dir[0] = 0;
+  }
+  if (! dir[0])                        // no project dir - get curdir as fallback
+  {
+    cp = getcwd (dir, sizeof(dir));
+    if (! cp) dir[0] = 0;
+  }
+  if (! dir[0]) return 1;      // no curdir accessible, nothing to change
+
+  fname[0] = 0;
+  const char *spath = file_box->get_submitted_path();// get name entered so far
+  if (spath) file_box->fs->extract_name (fname, spath);        // cut name from dir
+
+  if (fname[0]) file_box->fs->join_names (path, dir, fname);
+  else strcpy (path, dir);     // substitute old entered dir with project dir
+
+  // Not exactly sure what operations on FileBox are really important
+  file_box->fs->change_dir (dir);      // force it to recognize the new dir
+
+  // This updates all paths, sets current_path and submitted_path of FileBox,
+  // but in memory only, text fields in the dialog are not actualized.
+  // file_box->refresh() does not help to refresh text fields either.
+  // Therefore we have to apply a trick with closing FileBox and
+  // reopening it with the new generated path.
+  file_box->update_paths (path);
+
+  // Without updating history FileBox forgets our new dir
+  // and sets curdir to some old history item.
+  file_box->update_history();
+
+  file_box->reinit_path = 1;   // set flag to reopen FileBox afterwards
+  file_box->set_done(1); // temporarily close FileBox, will be reopened later
+
+  return 1;
+}
+
+BlendAlgebraToUsrlib::BlendAlgebraToUsrlib(BlendAlgebraFileBox *file_box,
+                                          int x, int y)
+  : BC_GenericButton (x, y, _("=>Userlib"))
+{
+  this->file_box = file_box;
+}
+
+int BlendAlgebraToUsrlib::handle_event()
+{
+  char *cp, fname[BCTEXTLEN], dir[BCTEXTLEN], path[BCTEXTLEN];
+
+  dir[0] = 0;
+  cp = getenv ("CIN_USERLIB");         // $HOME/.bcast5lib by default
+  if (cp) strcpy (dir, cp);
+  if (! dir[0])
+  {
+    cp = getenv ("HOME");              // evtl resolve as default via $HOME
+    if (cp) strcpy (dir, cp);
+    if (dir[0]) strcat (dir, "/.bcast5lib");
+    else
+    {
+      cp = getenv ("CIN_CONFIG");      // or via $CIN_CONFIG as fallback
+      if (cp) strcpy (dir, cp);
+      if (dir[0]) strcat (dir, "lib");
+    }
+  }
+  if (! dir[0]) return 1;      // no user libdir known, nothing to change
+
+  // The default user libdir for blend functions is $HOME/.bcast5lib/dlfcn/ba
+  // Ensure it is a directory, evtl create dir, if not - do nothing else
+  if (! file_box->fs->is_dir (dir)) file_box->fs->create_dir (dir);
+  if (! file_box->fs->is_dir (dir)) return 1;
+  strcat (dir, "/dlfcn");
+  if (! file_box->fs->is_dir (dir)) file_box->fs->create_dir (dir);
+  if (! file_box->fs->is_dir (dir)) return 1;
+  strcat (dir, "/ba");
+  if (! file_box->fs->is_dir (dir)) file_box->fs->create_dir (dir);
+  if (! file_box->fs->is_dir (dir)) return 1;
+
+  fname[0] = 0;
+  const char *spath = file_box->get_submitted_path();// get name entered so far
+  if (spath) file_box->fs->extract_name (fname, spath);        // cut name from dir
+  if (fname[0]) file_box->fs->join_names (path, dir, fname);
+  else strcpy (path, dir);     // substitute old entered dir with user libdir
+
+  // Reinitialize FileBox with the modified path
+  file_box->fs->change_dir (dir);
+  file_box->update_paths (path);
+  file_box->update_history();
+  file_box->reinit_path = 1;   // set flag to reopen FileBox afterwards
+  file_box->set_done(1); // temporarily close FileBox, will be reopened later
+
+  return 1;
+}
+
+BlendAlgebraToSyslib::BlendAlgebraToSyslib(BlendAlgebraFileBox *file_box,
+                                          int x, int y)
+  : BC_GenericButton (x, y, _("=>Syslib"))
+{
+  this->file_box = file_box;
+}
+
+int BlendAlgebraToSyslib::handle_event()
+{
+  char *cp, fname[BCTEXTLEN], dir[BCTEXTLEN], path[BCTEXTLEN];
+
+  dir[0] = 0;
+  cp = getenv ("CIN_DAT");     // Cinelerra installation directory (bin)
+  if (cp) strcpy (dir, cp);
+  if (! dir[0]) return 1;      // there is no default
+
+  // System libdir for blend functions is $CIN_DAT/dlfcn/ba (bin/dlfcn/ba).
+  // Ensure it is a directory, it must exist, if not - do nothing else
+  strcat (dir, "/dlfcn/ba");
+  if (! file_box->fs->is_dir (dir)) return 1;
+
+  fname[0] = 0;
+  const char *spath = file_box->get_submitted_path();// get name entered so far
+  if (spath) file_box->fs->extract_name (fname, spath);        // cut name from dir
+  if (fname[0]) file_box->fs->join_names (path, dir, fname);
+  else strcpy (path, dir);     // substitute that old dir with system libdir
+
+  // Reinitialize FileBox with the modified path
+  file_box->fs->change_dir (dir);
+  file_box->update_paths (path);
+  file_box->update_history();
+  file_box->reinit_path = 1;   // set flag to reopen FileBox afterwards
+  file_box->set_done(1); // temporarily close FileBox, will be reopened later
+
+  return 1;
+}
+
+BlendAlgebraCopyCurdir::BlendAlgebraCopyCurdir(BlendAlgebraFileBox *file_box,
+                                              int x, int y)
+  : BC_GenericButton (x, y, _("Copy to project"))
+{
+  this->file_box = file_box;
+}
+
+int BlendAlgebraCopyCurdir::handle_event()
+{
+  int ret;
+  char *cp, fname[BCTEXTLEN], dir[BCTEXTLEN], from_path[BCTEXTLEN],
+    to_path[BCTEXTLEN], cmd[3*BCTEXTLEN];
+
+  strcpy (dir, file_box->plugin->server->mwindow->session->filename);
+  if (dir[0])                  // first get current project directory
+  {
+    cp = strrchr (dir, '/');
+    if (cp) *cp = 0;
+    else dir[0] = 0;
+  }
+  if (! dir[0]) return 1;      // no curdir accessible, no copy target
+
+  fname[0] = from_path[0] = 0;
+  const char *spath = file_box->get_submitted_path();// get name entered so far
+  if (spath)
+  {
+    strcpy (from_path, spath);                 // this is copy source
+    file_box->fs->extract_name (fname, spath); // cut name from source dir
+  }
+  if (! (fname[0] && from_path[0])) return 1;  // no copy source ??
+
+  file_box->fs->join_names (to_path, dir, fname);      // this is copy target
+
+  if (! strcmp (from_path, to_path)) return 1; // source and target identical
+
+  if (file_box->fs->is_dir (from_path) || file_box->fs->is_dir (to_path))
+    return 1;                  // source and target must not be directories
+  if (access (from_path, R_OK))
+  {
+    eprintf (_("Blend Algebra: source file %s does not exist or not readable\n"),
+            from_path);
+    return 1;
+  }
+  if (! access (to_path, F_OK))
+  {
+    eprintf (_("Blend Algebra: target file %s exists, overwriting not allowed\n"),
+            to_path);
+    return 1;
+  }
+
+  // Now do copy operation
+  sprintf (cmd, "cp \"%s\" \"%s\"", from_path, to_path);
+#ifdef DEBUG
+  printf ("BlendAlgebraCopyCurdir::handle_event: executing %s\n", cmd);
+#endif
+  ret = system (cmd);
+  if (ret)
+  {
+    eprintf (_("Blend Algebra: copying %s to %s failed\nsee console printout for diagnostics\n"),
+            from_path, to_path);
+    return 1;
+  }
+
+  // Copying successful, now change dir to the location of the target
+  file_box->fs->change_dir (dir);
+  file_box->update_paths (to_path);
+  file_box->update_history();
+  file_box->reinit_path = 1;   // set flag to reopen FileBox afterwards
+  file_box->set_done(1); // temporarily close FileBox, will be reopened later
+
+  return 1;
+}
+
+BlendAlgebraCopyUsrlib::BlendAlgebraCopyUsrlib(BlendAlgebraFileBox *file_box,
+                                              int x, int y)
+  : BC_GenericButton (x, y, _("Copy to userlib"))
+{
+  this->file_box = file_box;
+}
+
+int BlendAlgebraCopyUsrlib::handle_event()
+{
+  int ret;
+  char *cp, fname[BCTEXTLEN], dir[BCTEXTLEN], from_path[BCTEXTLEN],
+    to_path[BCTEXTLEN], cmd[3*BCTEXTLEN];
+
+  dir[0] = 0;
+  cp = getenv ("CIN_USERLIB");         // $HOME/.bcast5lib by default
+  if (cp) strcpy (dir, cp);
+  if (! dir[0])
+  {
+    cp = getenv ("HOME");              // evtl resolve as default via $HOME
+    if (cp) strcpy (dir, cp);
+    if (dir[0]) strcat (dir, "/.bcast5lib");
+    else
+    {
+      cp = getenv ("CIN_CONFIG");      // or via $CIN_CONFIG as fallback
+      if (cp) strcpy (dir, cp);
+      if (dir[0]) strcat (dir, "lib");
+    }
+  }
+  if (! dir[0]) return 1;      // no user libdir known, no copy target
+
+  // Evtl create user libdir, if not successful - do nothing else
+  if (! file_box->fs->is_dir (dir)) file_box->fs->create_dir (dir);
+  if (! file_box->fs->is_dir (dir)) return 1;
+  strcat (dir, "/dlfcn");
+  if (! file_box->fs->is_dir (dir)) file_box->fs->create_dir (dir);
+  if (! file_box->fs->is_dir (dir)) return 1;
+  strcat (dir, "/ba");
+  if (! file_box->fs->is_dir (dir)) file_box->fs->create_dir (dir);
+  if (! file_box->fs->is_dir (dir)) return 1;
+
+  fname[0] = from_path[0] = 0;
+  const char *spath = file_box->get_submitted_path();// get name entered so far
+  if (spath)
+  {
+    strcpy (from_path, spath);                 // this is copy source
+    file_box->fs->extract_name (fname, spath); // cut name from source dir
+  }
+  if (! (fname[0] && from_path[0])) return 1;  // no copy source ??
+
+  file_box->fs->join_names (to_path, dir, fname);      // this is copy target
+
+  if (! strcmp (from_path, to_path)) return 1; // source and target identical
+
+  if (file_box->fs->is_dir (from_path) || file_box->fs->is_dir (to_path))
+    return 1;                  // source and target must not be directories
+  if (access (from_path, R_OK))
+  {
+    eprintf (_("Blend Algebra: source file %s does not exist or not readable\n"),
+            from_path);
+    return 1;
+  }
+  if (! access (to_path, F_OK))
+  {
+    eprintf (_("Blend Algebra: target file %s exists, overwriting not allowed\n"),
+            to_path);
+    return 1;
+  }
+
+  // Now do copy operation
+  sprintf (cmd, "cp \"%s\" \"%s\"", from_path, to_path);
+#ifdef DEBUG
+  printf ("BlendAlgebraCopyUsrlib::handle_event: executing %s\n", cmd);
+#endif
+  ret = system (cmd);
+  if (ret)
+  {
+    eprintf (_("Blend Algebra: copying %s to %s failed\nsee console printout for diagnostics\n"),
+            from_path, to_path);
+    return 1;
+  }
+
+  return 1; // Copying successful, but don't change directory to user libdir
+}
+
+BlendAlgebraFileEdit::BlendAlgebraFileEdit(BlendAlgebraFileBox *file_box,
+                                          int x, int y)
+  : BC_GenericButton (x, y, _("Edit..."))
+{
+  this->file_box = file_box;
+}
+
+int BlendAlgebraFileEdit::handle_event()
+{
+  char fname[BCTEXTLEN], dir[BCTEXTLEN], str[2*BCTEXTLEN];
+
+  fname[0] = 0;
+  const char *spath = file_box->get_submitted_path();// get name entered so far
+  if (spath) strcpy (fname, spath);
+  if (! fname[0])
+  {
+    eprintf (_("Blend Algebra: no function to edit, select source file first\n"));
+    return 1;
+  }
+
+  // Evtl make function path absolute by prepending project path to it
+  if (fname[0] != '/') // fname is relative, prepend current project path
+  {
+    strcpy (dir, file_box->plugin->server->mwindow->session->filename);
+    if (dir[0])
+    {
+      char *cp = strrchr (dir, '/');
+      if (cp)
+      {
+       cp[1] = 0;              // strip project filename off from project path
+       strcat (dir, fname);    // concatenate obtained path with function name
+       strcpy (fname, dir);
+      }
+    }
+  }
+  if (file_box->fs->is_dir (fname))
+  {
+    eprintf (_("Blend Algebra: cannot edit directory, select source file first\n"));
+    return 1;
+  }
+
+  // This will run configured external editor via perl script
+  // If editor start is not backgrounded, GUI will block until editor exits
+  sprintf(str, "\"%s/dlfcn/BlendAlgebraCompile.pl\" -edit \"%s\"",
+         getenv("CIN_DAT"), fname);
+#ifdef DEBUG
+  printf ("BlendAlgebraFileEdit::handle_event: executing:\n   %s\n", str);
+#endif
+  system (str);                                // runs configured external editor
+
+  file_box->plugin->func_lock->lock("BlendAlgebraFileEdit::handle_event");
+  BlendAlgebraTstamp = time(NULL);     // force refresh of dlopen'd functions
+#ifdef DEBUG
+  printf ("BlendAlgebraFileEdit::handle_event edited function %s\n   timestamp %s",
+         fname, ctime(&BlendAlgebraTstamp));
+#endif
+  file_box->plugin->func_lock->unlock();
+
+  return 1;
+}
+
+BlendAlgebraClipcolors::BlendAlgebraClipcolors(BlendAlgebra *plugin,
+                                              BlendAlgebraWindow *gui,
+                                              int x, int y)
+  : BC_CheckBox(x, y, plugin->config.clipcolors)
+{
+  this->plugin = plugin;
+  this->gui = gui;
+}
+
+int BlendAlgebraClipcolors::handle_event()
+{
+  plugin->config.clipcolors = get_value();
+  plugin->send_configure_change();
+  return 1;
+}
+
+BlendAlgebraParallel::BlendAlgebraParallel(BlendAlgebra *plugin,
+                                          BlendAlgebraWindow *gui,
+                                          int x, int y)
+  : BC_CheckBox(x, y, plugin->config.parallel)
+{
+  this->plugin = plugin;
+  this->gui = gui;
+}
+
+int BlendAlgebraParallel::handle_event()
+{
+  plugin->config.parallel = get_value();
+  plugin->send_configure_change();
+  return 1;
+}
+
+BlendAlgebraClearInput::BlendAlgebraClearInput(BlendAlgebra *plugin,
+                                              BlendAlgebraWindow *gui,
+                                              int x, int y)
+  : BC_CheckBox(x, y, plugin->config.clear_input)
+{
+  this->plugin = plugin;
+  this->gui = gui;
+}
+
+int BlendAlgebraClearInput::handle_event()
+{
+  plugin->config.clear_input = get_value();
+  plugin->send_configure_change();
+  return 1;
+}
+
+BlendAlgebraDirection::BlendAlgebraDirection(BlendAlgebra *plugin, int x, int y)
+  : BC_PopupMenu(x, y, xS(150),
+       BlendAlgebraConfig::direction_to_text(plugin->config.direction), 1)
+{
+  this->plugin = plugin;
+}
+
+void BlendAlgebraDirection::create_objects()
+{
+  add_item(new BC_MenuItem(BlendAlgebraConfig::direction_to_text(
+                            BlendAlgebraConfig::TOP_FIRST)));
+  add_item(new BC_MenuItem(BlendAlgebraConfig::direction_to_text(
+                            BlendAlgebraConfig::BOTTOM_FIRST)));
+}
+
+int BlendAlgebraDirection::handle_event()
+{
+  char *text = get_text();
+
+  if(!strcmp(text, BlendAlgebraConfig::direction_to_text(
+              BlendAlgebraConfig::TOP_FIRST)))
+    plugin->config.direction = BlendAlgebraConfig::TOP_FIRST;
+  else if(!strcmp(text, BlendAlgebraConfig::direction_to_text(
+                   BlendAlgebraConfig::BOTTOM_FIRST)))
+    plugin->config.direction = BlendAlgebraConfig::BOTTOM_FIRST;
+
+  plugin->send_configure_change();
+  return 1;
+}
+
+BlendAlgebraOutput::BlendAlgebraOutput(BlendAlgebra *plugin, int x, int y)
+  : BC_PopupMenu(x, y, xS(100),
+       BlendAlgebraConfig::output_to_text(plugin->config.output_track), 1)
+{
+  this->plugin = plugin;
+}
+
+void BlendAlgebraOutput::create_objects()
+{
+  add_item(new BC_MenuItem(BlendAlgebraConfig::output_to_text(
+                            BlendAlgebraConfig::TOP)));
+  add_item(new BC_MenuItem(BlendAlgebraConfig::output_to_text(
+                            BlendAlgebraConfig::BOTTOM)));
+}
+
+int BlendAlgebraOutput::handle_event()
+{
+  char *text = get_text();
+
+  if(!strcmp(text, BlendAlgebraConfig::output_to_text(
+              BlendAlgebraConfig::TOP)))
+    plugin->config.output_track = BlendAlgebraConfig::TOP;
+  else if(!strcmp(text, BlendAlgebraConfig::output_to_text(
+                   BlendAlgebraConfig::BOTTOM)))
+    plugin->config.output_track = BlendAlgebraConfig::BOTTOM;
+
+  plugin->send_configure_change();
+  return 1;
+}
+
+BlendAlgebraColorspace::BlendAlgebraColorspace(BlendAlgebra *plugin,
+                                              int x, int y)
+  : BC_PopupMenu(x, y, xS(150),
+       BlendAlgebraConfig::colorspace_to_text(plugin->config.colorspace), 1)
+{
+  this->plugin = plugin;
+}
+
+void BlendAlgebraColorspace::create_objects()
+{
+  add_item(new BC_MenuItem(BlendAlgebraConfig::colorspace_to_text(
+                            BlendAlgebraConfig::AUTO)));
+  add_item(new BC_MenuItem(BlendAlgebraConfig::colorspace_to_text(
+                            BlendAlgebraConfig::RGB)));
+  add_item(new BC_MenuItem(BlendAlgebraConfig::colorspace_to_text(
+                            BlendAlgebraConfig::YUV)));
+  add_item(new BC_MenuItem(BlendAlgebraConfig::colorspace_to_text(
+                            BlendAlgebraConfig::HSV)));
+  add_item(new BC_MenuItem(BlendAlgebraConfig::colorspace_to_text(
+                            BlendAlgebraConfig::PROJECT)));
+}
+
+int BlendAlgebraColorspace::handle_event()
+{
+  char *text = get_text();
+
+  if(!strcmp(text, BlendAlgebraConfig::colorspace_to_text(
+              BlendAlgebraConfig::AUTO)))
+    plugin->config.colorspace = BlendAlgebraConfig::AUTO;
+  else if(!strcmp(text, BlendAlgebraConfig::colorspace_to_text(
+                   BlendAlgebraConfig::RGB)))
+    plugin->config.colorspace = BlendAlgebraConfig::RGB;
+  else if(!strcmp(text, BlendAlgebraConfig::colorspace_to_text(
+                   BlendAlgebraConfig::YUV)))
+    plugin->config.colorspace = BlendAlgebraConfig::YUV;
+  else if(!strcmp(text, BlendAlgebraConfig::colorspace_to_text(
+                   BlendAlgebraConfig::HSV)))
+    plugin->config.colorspace = BlendAlgebraConfig::HSV;
+  else if(!strcmp(text, BlendAlgebraConfig::colorspace_to_text(
+                   BlendAlgebraConfig::PROJECT)))
+    plugin->config.colorspace = BlendAlgebraConfig::PROJECT;
+
+  plugin->send_configure_change();
+  return 1;
+}
+
+BlendAlgebraKeyColor::BlendAlgebraKeyColor (BlendAlgebra *plugin,
+                                           BlendAlgebraWindow *gui,
+                                           int x, int y)
+  : BC_GenericButton (x, y, _("Select key color..."))
+{
+  this->plugin = plugin;
+  this->gui = gui;
+}
+
+int BlendAlgebraKeyColor::handle_event()
+{
+  gui->color_thread->start_window (plugin->config.get_key_color(), 0xff);
+  return 1;
+}
+
+BlendAlgebraColorPicker::BlendAlgebraColorPicker (BlendAlgebra *plugin,
+                                                 BlendAlgebraWindow *gui,
+                                                 int x, int y)
+  : BC_GenericButton (x, y, _("Get from color picker"))
+{
+  this->plugin = plugin;
+  this->gui = gui;
+}
+
+int BlendAlgebraColorPicker::handle_event()
+{
+  plugin->config.red   = plugin->get_red();
+  plugin->config.green = plugin->get_green();
+  plugin->config.blue  = plugin->get_blue();
+
+  gui->update_key_sample();
+
+  plugin->send_configure_change();
+  return 1;
+}
+
+BlendAlgebraColorThread::BlendAlgebraColorThread (BlendAlgebra * plugin,
+                                                 BlendAlgebraWindow * gui)
+  : ColorPicker (0, _("Select color"))
+{
+  this->plugin = plugin;
+  this->gui = gui;
+}
+
+int BlendAlgebraColorThread::handle_new_color (int output, int alpha)
+{
+  plugin->config.red   = (float) ((output & 0xff0000) >> 16) / 255;
+  plugin->config.green = (float) ((output & 0x00ff00) >>  8) / 255;
+  plugin->config.blue  = (float) ((output & 0x0000ff)      ) / 255;
+
+  get_gui()->unlock_window();
+  gui->lock_window("BlendAlgebraColorThread::handle_new_color");
+  gui->update_key_sample();
+  gui->unlock_window();
+  get_gui()->lock_window("BlendAlgebraColorThread::handle_new_color");
+
+  plugin->send_configure_change();
+  return 1;
+}
+
+BlendAlgebraAlphaText::BlendAlgebraAlphaText(BlendAlgebra *plugin,
+                                            BlendAlgebraWindow *gui,
+                                            BlendAlgebraAlphaSlider *slider,
+                                            int x, int y,
+                                            float min, float max,
+                                            float *output)
+  : BC_TumbleTextBox(gui, *output, min, max, x, y, xS(60), 2)
+{
+  this->plugin = plugin;
+  this->gui = gui;
+  this->slider = slider;
+  this->min = min;
+  this->max = max;
+  this->output = output;
+  set_increment(0.01);
+}
+
+BlendAlgebraAlphaText::~BlendAlgebraAlphaText()
+{
+}
+
+int BlendAlgebraAlphaText::handle_event()
+{
+  *output = atof(get_text());
+  if(*output > max) *output = max;
+  if(*output < min) *output = min;
+  slider->update(*output);
+
+  plugin->send_configure_change();
+  return 1;
+}
+
+BlendAlgebraAlphaSlider::BlendAlgebraAlphaSlider(BlendAlgebra *plugin,
+                                                BlendAlgebraAlphaText *text,
+                                                int x, int y, int w,
+                                                float min, float max,
+                                                float *output)
+  : BC_FSlider(x, y, 0, w, w, min, max, *output)
+{
+  this->plugin = plugin;
+  this->text = text;
+  this->output = output;
+  set_precision(0.01);
+  enable_show_value(0);                // Hide caption
+}
+
+BlendAlgebraAlphaSlider::~BlendAlgebraAlphaSlider()
+{
+}
+
+int BlendAlgebraAlphaSlider::handle_event()
+{
+  *output = get_value();
+  text->update(*output);
+
+  plugin->send_configure_change();
+  return 1;
+}
+
+BlendAlgebraWindow::BlendAlgebraWindow(BlendAlgebra *plugin)
+  : PluginClientWindow(plugin, xS(450), yS(410), xS(450), yS(410), 0)
+{
+  this->plugin = plugin;
+  color_thread = 0;
+  editing_lock = new Mutex("BlendAlgebraWindow::editing_lock");
+  editing = 0;
+}
+
+BlendAlgebraWindow::~BlendAlgebraWindow()
+{
+  delete color_thread;
+  delete editing_lock;
+}
+
+void BlendAlgebraWindow::create_objects()
+{
+  int xs5 = xS(5), xs10 = xS(10), xs20 = xS(20);
+  int ys5 = yS(5), ys10 = yS(10), ys20 = yS(20), ys30 = yS(30), ys40 = yS(40);
+  int x = xs10, y = ys10, x2;
+  BC_Title *title;
+  BC_TitleBar *title_bar;
+
+  // Programming section
+  add_subwindow (title_bar =
+                new BC_TitleBar(x, y, get_w()-2*x, xs20, xs10,
+                                _("Blend programming environment")));
+
+  y += ys30;
+  add_subwindow(title = new BC_Title(x, y, _("Function:")));
+  add_subwindow(funcname =
+               new BlendAlgebraFuncname(plugin, plugin->config.funcname, this,
+                                        x + title->get_w() + xs5, y));
+
+  y += ys30;
+  add_subwindow(file_button = new BlendAlgebraFileButton(plugin, this, x, y));
+  x2 = x+file_button->get_w()+xs5;
+  add_subwindow(edit_button = new BlendAlgebraEdit(plugin, this, x2, y));
+  x2 += edit_button->get_w()+xs5;
+  add_subwindow(refresh_button = new BlendAlgebraRefresh(plugin, this, x2, y));
+  x2 += refresh_button->get_w()+xs5;
+  add_subwindow(detach_button = new BlendAlgebraDetach(plugin, this, x2, y));
+
+  y += ys30;
+  add_subwindow(title = new BC_Title(x, y, _("Color space:")));
+  add_subwindow(colorspace =
+               new BlendAlgebraColorspace(plugin,
+                                          x + title->get_w() + xs5, y));
+  colorspace->create_objects();
+
+  x2 = x+title->get_w()+colorspace->get_w()+xs10+xs10;
+  add_subwindow(title = new BC_Title(x2, y, _("Parallelize processing")));
+  add_subwindow(parallel =
+               new BlendAlgebraParallel(plugin, this,
+                                        x2 + title->get_w() + xs5, y));
+
+  // Supplementary color section
+  y += ys40;
+  add_subwindow (title_bar =
+                new BC_TitleBar(x, y, get_w()-2*x, xs20, xs10,
+                                _("Supplementary color selection")));
+
+  y += ys30;
+  add_subwindow(title =
+               new BC_Title(x, y, _("Chroma key or substitution color:")));
+  add_subwindow(key_sample = new BC_SubWindow(x + title->get_w() + xs5, y,
+                                             xS(150), yS(50)));
+  y += ys20+ys5;
+  add_subwindow(title = new BC_Title(x, y, _("Clip color values")));
+  add_subwindow(clipcolors =
+               new BlendAlgebraClipcolors(plugin, this,
+                                          x + title->get_w() + xs5, y));
+
+  y += ys30+ys5;
+  add_subwindow(key_color = new BlendAlgebraKeyColor(plugin, this, x, y));
+  x2 = x+key_color->get_w()+xs5;
+  add_subwindow(color_picker =
+               new BlendAlgebraColorPicker(plugin, this, x2, y));
+
+  y += ys30+ys5;
+  add_subwindow(title = new BC_Title(x, y, _("Substitution opacity:")));
+  alpha_text = new BlendAlgebraAlphaText (plugin, this, 0,
+                                         x+title->get_w()+xs10+xs5+xS(210),
+                                         y, 0, 1, &plugin->config.alpha);
+  alpha_text->create_objects();
+  key_alpha = new BlendAlgebraAlphaSlider (plugin, alpha_text,
+                                          x + title->get_w() + xs5, y,
+                                          xS(210), 0, 1,
+                                          &plugin->config.alpha);
+  add_subwindow(key_alpha);
+  alpha_text->slider = key_alpha;
+
+  // Track arrangement section
+  y += ys40;
+  add_subwindow (title_bar =
+                new BC_TitleBar(x, y, get_w()-2*x, xs20, xs10,
+                                _("Processed tracks arrangement")));
+
+  y += ys30;
+  add_subwindow(title = new BC_Title(x, y, _("Track order:")));
+  add_subwindow(direction =
+               new BlendAlgebraDirection(plugin,
+                                         x + title->get_w() + xs5, y));
+  direction->create_objects();
+  x2 = x+title->get_w()+direction->get_w()+xs10;
+  add_subwindow(title = new BC_Title(x2, y, _("Output track:")));
+  add_subwindow(output =
+               new BlendAlgebraOutput(plugin, x2 + title->get_w() + xs5, y));
+  output->create_objects();
+
+  y += ys30;
+  add_subwindow(title =
+               new BC_Title(x, y,
+                            _("Hide input tracks, use output exclusively")));
+  add_subwindow(clear_input =
+               new BlendAlgebraClearInput(plugin, this,
+                                          x + title->get_w() + xs5, y));
+
+  color_thread = new BlendAlgebraColorThread(plugin, this);
+
+  update_key_sample();
+  show_window();
+  flush();
+}
+
+void BlendAlgebraWindow::update_key_sample()
+{
+  key_sample->set_color (plugin->config.get_key_color());
+  key_sample->draw_box (0, 0, key_sample->get_w(), key_sample->get_h());
+  key_sample->set_color (BLACK);
+  key_sample->draw_rectangle (0, 0, key_sample->get_w(), key_sample->get_h());
+  key_sample->flash ();
+}
+
+void BlendAlgebraWindow::done_event()
+{
+  color_thread->close_window();
+}
+
+int BlendAlgebraWindow::close_event()
+{
+  color_thread->close_window();
+  file_button->stop();
+  set_done(1);
+  return 1;
+}
+
+int BlendAlgebraWindow::hide_window (int flush)
+{
+  color_thread->close_window();
+  file_button->stop();
+  return BC_WindowBase::hide_window (flush);
+}
+
+////////////////////////////////////////////
+// Plugin main class implementation
+////////////////////////////////////////////
+
+BlendAlgebraFunc::BlendAlgebraFunc()
+{
+  src[0] =  0;
+  handle =  0;
+  proc   =  0;
+  init   =  0;
+  tstamp = -1;
+}
+
+BlendAlgebraFunc::~BlendAlgebraFunc()
+{
+  if (handle)
+  {
+#ifdef DEBUG
+    printf ("BlendAlgebraFunc destructor detaching function dlclose(%s)\n",
+           src);
+#endif
+    dlclose (handle);
+  }
+}
+
+BlendAlgebra::BlendAlgebra(PluginServer *server)
+  : PluginVClient(server)
+{
+  BlendAlgebraTstamp = time(NULL);
+  inspect_configuration = 1;                   // force initial configuration
+  curr_func_no = -1;
+  func_lock = new Mutex("BlendAlgebra::func_lock");
+  engine = 0;
+#ifdef DEBUG
+  printf ("BlendAlgebra constructor timestamp %s", ctime(&BlendAlgebraTstamp));
+#endif
+}
+
+BlendAlgebra::~BlendAlgebra()
+{
+  if (engine) delete engine;
+  delete func_lock;
+#ifdef DEBUG
+  printf ("BlendAlgebra destructor removing all %d functions\n",
+         funclist.total);
+#endif
+  funclist.remove_all_objects();
+  curr_func.handle = 0;
+}
+
+int BlendAlgebra::process_buffer(VFrame **frame,
+                                int64_t start_position,
+                                double frame_rate)
+{
+  BlendAlgebraFunc *ptr;
+  int refresh_eprintf = 0;
+
+  // Mocking up with function shared object if it might get modified
+  // Not sure if plugin locking is needed for this separate processing instance
+  func_lock->lock("BlendAlgebra::process_buffer");
+
+  // First check if function name was changed
+  if (load_configuration() || inspect_configuration) // function might change
+  {
+    inspect_configuration = 0; // do once after change or after creation
+    if (strcmp (curr_func.src, config.funcname))       // function changed
+    {
+      curr_func.src[0] = 0;
+      curr_func.handle = 0;
+      curr_func.proc   = 0;
+      curr_func.init   = 0;
+      curr_func.tstamp = -1;
+      curr_func_no = -1;
+      if (config.funcname[0])                  // function name not empty
+      {
+       refresh_eprintf = 1;                    // probably new function
+       strcpy (curr_func.src, config.funcname);
+#ifdef DEBUG
+       printf ("BlendAlgebra::process_buffer searching function %s out of %d\n",
+               curr_func.src, funclist.total);
+#endif
+       for (int i=0; i<funclist.total; i++)    // cache of linked functions
+       {
+         ptr = funclist[i];
+         if (! strcmp (curr_func.src, ptr->src)) // cached function found
+         {
+           curr_func.handle = ptr->handle;
+           curr_func.proc   = ptr->proc;
+           curr_func.init   = ptr->init;
+           curr_func.tstamp = ptr->tstamp;
+           curr_func_no = i;
+#ifdef DEBUG
+           printf ("BlendAlgebra::process_buffer cached function %s found: %d\n   timestamp %s",
+                   ptr->src, curr_func_no, ctime(&ptr->tstamp));
+#endif
+           break;
+         }                                     // if cached function found
+       }                                       // for funclist.total
+      }                                                // if function name not empty
+    }                                          // if function changed
+  }                                            // if load_configuration()
+
+  // Now ensure that function binary is up to date, evtl recompile/relink it
+  if (curr_func.src[0])                                // current function not empty
+  {
+    if (curr_func.tstamp == -1 || BlendAlgebraTstamp > curr_func.tstamp)
+    {          // function first seen or linked before last config change
+      char str[BCTEXTLEN*2], dir[BCTEXTLEN], path[BCTEXTLEN];
+      time_t tstamp = -1;
+
+      // Evtl make function path absolute by prepending project path to it
+      strcpy (path, curr_func.src);
+      if (path[0] != '/') // path is relative, prepend current project path
+      {
+       strcpy (dir, server->mwindow->session->filename);
+       if (dir[0])
+       {
+         char *cp = strrchr (dir, '/');
+         if (cp)
+         {
+           cp[1] = 0;          // strip project filename off from project path
+           strcat (dir, path); // concatenate obtained path with function name
+           strcpy (path, dir);
+         }
+       }
+      }
+
+      // Try to lock function filename against concurrent compiler runs
+      // This kind of locking seems definitely reasonable here
+      struct flock locks;
+      locks.l_whence = SEEK_SET;
+      locks.l_start = locks.l_len = 0;
+      int fd = open (path, O_RDWR);
+      if (fd > -1)             // if lock cannot be set, ignore this for now
+      {
+       locks.l_type = F_WRLCK;
+       fcntl (fd, F_SETLKW, &locks); // try to wait for lock, ignoring errors
+       sprintf(str, "\"%s/dlfcn/BlendAlgebraCompile.pl\" \"%s\"",
+               getenv("CIN_DAT"), path);
+#ifdef DEBUG
+       printf ("BlendAlgebra::process_buffer\n   curr_func.tstamp %s",
+               ctime(&curr_func.tstamp));
+       printf ("   global tstamp %s   executing %s\n",
+               ctime(&BlendAlgebraTstamp), str);
+#endif
+       system (str);   // evtl recompile function if source newer than object
+       if (path[0] == '/') sprintf (str, "%s.so", path);
+       else sprintf (str, "./%s.so", path);    // dlopen requires a slash
+       struct stat statbuf;
+       if (0 == stat (str, &statbuf)) tstamp = statbuf.st_mtime;
+      }
+      else                             // function source cannot be opened
+      {
+#ifdef DEBUG
+       printf ("BlendAlgebra::process_buffer cannot access function %s\n",
+               curr_func.src);
+#endif
+      }
+
+      // Now test if function relinking needed, make function cache consistent
+      if (tstamp == -1)
+      {                // either function does not exist or compilation unsuccessful
+       if (fd > -1)
+         eprintf (_("Blend Algebra: compilation of function %s failed\nsee console printout for diagnostics\n"),
+                  curr_func.src);
+       if (curr_func_no >= 0)
+       {                                               // detach old function
+         if (funclist[curr_func_no]->handle)
+         {
+#ifdef DEBUG
+           printf ("BlendAlgebra::process_buffer detaching function %d dlclose(%s)\n",
+                   curr_func_no, funclist[curr_func_no]->src);
+#endif
+           dlclose (funclist[curr_func_no]->handle);
+         }
+#ifdef DEBUG
+         printf ("BlendAlgebra::process_buffer removing function %d (%s)\n",
+                 curr_func_no, curr_func.src);
+#endif
+         funclist[curr_func_no]->src[0] = 0;
+         funclist[curr_func_no]->handle = 0;
+         funclist[curr_func_no]->proc   = 0;
+         funclist[curr_func_no]->init   = 0;
+         funclist[curr_func_no]->tstamp = -1;
+         funclist.remove_object_number (curr_func_no);
+         curr_func_no = -1;
+       }
+       curr_func.handle = 0;
+       curr_func.proc   = 0;
+       curr_func.init   = 0;
+       curr_func.tstamp = time (NULL);
+      }
+      else if (curr_func.tstamp == -1 || tstamp > curr_func.tstamp)
+      {                                // function first seen or edited after linkage
+#ifdef DEBUG
+       printf ("BlendAlgebra::process_buffer\n   curr_func.tstamp %s",
+               ctime(&curr_func.tstamp));
+       printf ("   tstamp %s   relinking %s\n", ctime(&tstamp), str);
+#endif
+       if (curr_func_no >= 0 && funclist[curr_func_no]->handle)
+       {                                               // detach old function
+#ifdef DEBUG
+         printf ("BlendAlgebra::process_buffer detaching function %d dlclose(%s)\n",
+                 curr_func_no, funclist[curr_func_no]->src);
+#endif
+         dlclose (funclist[curr_func_no]->handle);
+         funclist[curr_func_no]->src[0] = 0;
+         funclist[curr_func_no]->handle = 0;
+         funclist[curr_func_no]->proc   = 0;
+         funclist[curr_func_no]->init   = 0;
+         funclist[curr_func_no]->tstamp = -1;
+       }
+       curr_func.proc   = 0;
+       curr_func.init   = 0;
+       curr_func.handle = dlopen (str, RTLD_NOW);      // shared object handle
+#ifdef DEBUG
+       printf ("BlendAlgebra::process_buffer dlopen(%s)=%p\n",
+               str, curr_func.handle);
+#endif
+       if (curr_func.handle)   // inquire necessary extern entry points
+       {                       // baProc is mandatory, baInit optional
+         curr_func.init = (BAF_init) dlsym (curr_func.handle, "baInit");
+         if (curr_func.init == NULL)   // not a problem, we can continue
+           printf (_("Blend Algebra: optional entry point \"baInit\" for function %s not found:\n%s\n"),
+                   str, dlerror());
+         curr_func.proc = (BAF_proc) dlsym (curr_func.handle, "baProc");
+#ifdef DEBUG
+         printf ("BlendAlgebra::process_buffer dlsym(%s) init=%p proc=%p\n",
+                 curr_func.src, curr_func.init, curr_func.proc);
+#endif
+         if (curr_func.proc == NULL)   // nothing to do if this not working
+         {
+           eprintf (_("Blend Algebra: entry point \"baProc\" for function %s not found:\n%s\n"),
+                    str, dlerror());
+#ifdef DEBUG
+           printf ("BlendAlgebra::process_buffer dlclose(%s)\n",
+                   curr_func.src);
+#endif
+           dlclose (curr_func.handle);
+           curr_func.handle = 0;
+           curr_func.init   = 0;
+         }
+       }
+       else
+         eprintf (_("Blend Algebra: dynamic load of function %s failed:\n%s\n"),
+                  str, dlerror());
+       curr_func.tstamp = time (NULL);
+       if (curr_func_no >= 0)          // function was already in cache
+       {
+         if (curr_func.proc)                   // update object in cache
+         {
+           strcpy (funclist[curr_func_no]->src, curr_func.src);
+           funclist[curr_func_no]->handle = curr_func.handle;
+           funclist[curr_func_no]->proc   = curr_func.proc;
+           funclist[curr_func_no]->init   = curr_func.init;
+           funclist[curr_func_no]->tstamp = curr_func.tstamp;
+#ifdef DEBUG
+           printf ("BlendAlgebra::process_buffer function %d (%s) updated\n   timestamp %s",
+                   curr_func_no, curr_func.src, ctime(&curr_func.tstamp));
+#endif
+         }
+         else                                  // remove outdated function
+         {
+#ifdef DEBUG
+           printf ("BlendAlgebra::process_buffer removing function %d (%s)\n",
+                   curr_func_no, curr_func.src);
+#endif
+           funclist.remove_object_number (curr_func_no);
+           curr_func_no = -1;
+         }
+       }
+       else if (curr_func.proc)        // add new linked function to cache
+       {
+         curr_func_no = funclist.total;
+         ptr = new BlendAlgebraFunc;
+         funclist.append (ptr);
+         strcpy (ptr->src, curr_func.src);
+         ptr->handle = curr_func.handle;
+         ptr->proc   = curr_func.proc;
+         ptr->init   = curr_func.init;
+         ptr->tstamp = curr_func.tstamp;
+#ifdef DEBUG
+         printf ("BlendAlgebra::process_buffer function %d (%s) appended\n   timestamp %s",
+                 curr_func_no, ptr->src, ctime(&ptr->tstamp));
+#endif
+       }                               // if function in cache
+      }
+      else                             // function does not need relinking
+      {
+       curr_func.tstamp = time (NULL);
+       if (curr_func_no >= 0)                  // just update timestamp
+         funclist[curr_func_no]->tstamp = curr_func.tstamp;
+#ifdef DEBUG
+       printf ("BlendAlgebra::process_buffer function %s does not need relinking\n   cache number %d timestamp %s",
+               curr_func.src, curr_func_no, ctime(&curr_func.tstamp));
+#endif
+      }                                                // if tstamp
+
+      if (fd > -1)                             // unlock function
+      {
+       locks.l_type = F_UNLCK;
+       fcntl (fd, F_SETLK, &locks);
+       close (fd);
+      }
+    }                                  // if function first seen or changed
+  }                                    // if current function not empty
+
+  func_lock->unlock();         // end mocking up with function shared object
+
+  // Now prepare the important pars and read all involved frames...
+  layers = get_total_buffers();
+  width  = frame[0]->get_w();
+  height = frame[0]->get_h();
+  for (int l=0; l<layers; l++)
+    read_frame (frame[l], l, start_position, frame_rate, 0);
+  this->frame = frame;
+
+  if (curr_func.proc == NULL) return 0;                // no function, nothing to do
+
+  color_proj = frame[0]->get_color_model(); // internal colorspace of project
+  if (color_proj == BC_RGBA_FLOAT ||
+      color_proj == BC_RGBA8888   ||
+      color_proj == BC_YUVA8888)
+    has_alpha = 1;                     // has alpha channel
+  else has_alpha = 0;
+  color_work = config.colorspace;      // will be requested from the function
+  int color_arg  = color_work;
+  int min_layers = layers;             // function's min required no of tracks
+  int parallel   = 0;                  // assumed not parallelized by default
+  if (curr_func.init != NULL)          // ask function about important pars
+    curr_func.init (&color_arg, color_proj, &min_layers, layers,
+                   &parallel, config.parallel, width, height, has_alpha);
+  if (min_layers > layers)
+  {
+    if (refresh_eprintf)
+      eprintf (_("Blend Algebra: cannot execute function %s:\nrequires %d tracks to process, has only %d tracks\n"),
+              curr_func.src, min_layers, layers);
+    return 0;                          // too few tracks to do anything
+  }
+  if (color_work == BlendAlgebraConfig::AUTO) color_work = color_arg;
+  if (color_work == BlendAlgebraConfig::AUTO)
+    color_work = BlendAlgebraConfig::PROJECT; // still not defined, dont change
+  if (! config.parallel) parallel = 0;         // parallelism not requested
+
+  // In case of a fatal bug in the user defined function (SIGFPE, SIGSEGV,...)
+  // Cinelerra perhaps will crash. Unfortunately we cannot handle the signals
+  // here: signal handler as set by sigaction() is process-wide, the same
+  // for all threads. And Cinelerra already uses its own signal handler
+  // for debug purposes which we are not allowed to overwrite with our one.
+  // Infinities and NaN will be trapped and substituted with configured color.
+  // Here we prepare key color components in three possible color spaces
+  // from this configured color. Used to substitute NaN or infinities.
+  // Can be used also inside user's function like a chroma key.
+  rgb_r = config.red;          // user's configured color is always RGB
+  rgb_g = config.green;
+  rgb_b = config.blue;
+  YUV::yuv.rgb_to_yuv_f (rgb_r, rgb_g, rgb_b, yuv_y, yuv_u, yuv_v); // make YUV
+  HSV::rgb_to_hsv       (rgb_r, rgb_g, rgb_b, hsv_h, hsv_s, hsv_v); // make HSV
+  key_a = config.alpha;                // user's configured alpha
+
+  if (parallel)                // parallelism desired, and supoported by the function
+  {
+    if (! engine)
+      engine = new BlendAlgebraEngine (this,
+                                      get_project_smp() + 1,
+                                      get_project_smp() + 1);
+    engine->process_packages();
+  }
+  else process_frames (0, height);     // process everything sequential
+
+  return 0;                                            // WHEW !!!
+}
+
+// Now comes the whole math. User's function will get everything in float.
+// If the project's color model is 8-bit, pixels will be converted to float.
+// Then, if requested, pixels will be converted to working color space
+// (RGB, YUV, or HSV) which is required by the function. After processing,
+// all the conversions will be rolled back in the reverse order for the result.
+// This universal function is called via loadbalance multithreading engine
+// as well as directly if parallelism not requested or not supported
+
+void BlendAlgebra::process_frames (int y1, int y2)
+{
+  float r[layers], g[layers], b[layers], a[layers];
+  float rk, gk, bk, yk, uk, vk, out_r, out_g, out_b, out_a;
+  int k, l, start, step, arg_out, trk_out;
+
+  // start, step define function argument indices relative to track numbers
+  // trk_out defines result index relative to track numbers
+  // arg_out defines result index relative to function args (start, step)
+  // arg_out used before function call to preinitialize future result
+  // trk_out used after function call to place result into the right track
+  // if function does not set the result, output track stays unmodified
+  if (config.direction == BlendAlgebraConfig::BOTTOM_FIRST)
+  {
+    start = layers-1;
+    step  = -1;
+    if (config.output_track == BlendAlgebraConfig::TOP) arg_out = layers-1;
+    else arg_out = 0;
+  }
+  else
+  {
+    start = 0;
+    step  = 1;
+    if (config.output_track == BlendAlgebraConfig::TOP) arg_out = 0;
+    else arg_out = layers-1;
+  }
+  if (config.output_track == BlendAlgebraConfig::TOP) trk_out = 0;
+  else trk_out = layers-1;
+
+  int clip_colors = config.clipcolors;         // clipping floats is optional
+  yk = uk = vk = 0;                            // to make gcc -O2 happy
+
+  switch (color_proj)
+  {
+  case BC_RGB_FLOAT:           // RGB  [ 0 .. 1 ], out of bounds possible
+  case BC_RGBA_FLOAT:          // RGBA [ 0 .. 1 ], out of bounds possible
+    for (int i=y1; i<y2; i++)          // scan all rows
+    {
+      float *row[layers];
+      for (l=0; l<layers; l++) row[l] = (float *)frame[l]->get_rows()[i];
+      for (int j=0; j<width; j++)      // scan all pixels
+      {
+       k = start;
+       for (l=0; l<layers; l++)        // convert source frames to args
+       {
+         if (color_work == BlendAlgebraConfig::YUV)
+         {
+           YUV::yuv.rgb_to_yuv_f (row[l][0], row[l][1], row[l][2],
+                                  r[k],        // Y pixel to blend
+                                  g[k],        // U pixel
+                                  b[k]);       // V pixel
+           yk = yuv_y;                         // user's key color (YUV)
+           uk = yuv_u;
+           vk = yuv_v;
+         }
+         else if (color_work == BlendAlgebraConfig::HSV)
+         {
+           HSV::rgb_to_hsv (row[l][0], row[l][1], row[l][2],
+                            r[k],              // H pixel to blend
+                            g[k],              // S pixel
+                            b[k]);             // V pixel
+           yk = hsv_h;                         // user's key color (HSV)
+           uk = hsv_s;
+           vk = hsv_v;
+         }
+         else  // either RGB or by PROJECT, no change
+         {
+           r[k] = row[l][0];                   // RGB pixel to blend
+           g[k] = row[l][1];
+           b[k] = row[l][2];
+           yk   = rgb_r;                       // user's key color (RGB)
+           uk   = rgb_g;
+           vk   = rgb_b;
+         }     // if color_work
+         a[k] = has_alpha ? row[l][3] : 1;
+         if (config.clear_input)       // leave output track exclusively
+         {
+           row[l][0] = row[l][1] = row[l][2] = 0;
+           if (has_alpha) row[l][3] = 0;
+         }
+         k += step;
+       }       // scan tracks for l = 0 .. layers
+       out_r = r[arg_out];     // preinitialize args holding output results
+       out_g = g[arg_out];
+       out_b = b[arg_out];
+       out_a = a[arg_out];
+
+       // Call user function
+       curr_func.proc (layers, r, g, b, a, yk, uk, vk, key_a,
+                       &out_r, &out_g, &out_b, &out_a,
+                       j, i, width, height, has_alpha);
+
+       if (clip_colors) CLAMP (out_a, 0, 1);
+       if (color_work == BlendAlgebraConfig::YUV)
+       {
+         if (clip_colors)
+         {
+           CLAMP (out_r,  0,   1);
+           CLAMP (out_g, -0.5, 0.5);
+           CLAMP (out_b, -0.5, 0.5);
+         }
+         if (! (isfinite(out_r) && isfinite(out_g) &&
+                isfinite(out_b) && isfinite(out_a)))
+         {     // substitute NaN or unclipped infinity with user's color (YUV)
+           out_r = yuv_y;
+           out_g = yuv_u;
+           out_b = yuv_v;
+           out_a = key_a;
+         }
+         YUV::yuv.yuv_to_rgb_f (row[trk_out][0],       // R
+                                row[trk_out][1],       // G
+                                row[trk_out][2],       // B
+                                out_r,                 // Y
+                                out_g,                 // U
+                                out_b);                // V
+       }
+       else if (color_work == BlendAlgebraConfig::HSV)
+       {
+         if (clip_colors)
+         {
+           if (isfinite(out_r) && (out_r < 0 || out_r >= 360))
+             out_r -= floor(out_r/360)*360;    // cannot clamp infinity here
+           CLAMP (out_g, 0, 1);
+           CLAMP (out_b, 0, 1);
+         }
+         if (! (isfinite(out_r) && isfinite(out_g) &&
+                isfinite(out_b) && isfinite(out_a)))
+         {     // substitute NaN or unclipped infinity with user's color (HSV)
+           out_r = hsv_h;
+           out_g = hsv_s;
+           out_b = hsv_v;
+           out_a = key_a;
+         }
+         HSV::hsv_to_rgb (row[trk_out][0], row[trk_out][1], row[trk_out][2],
+                          out_r,               // H
+                          out_g,               // S
+                          out_b);              // V
+       }
+       else    // either RGB or by PROJECT, no change, clip only
+       {
+         if (clip_colors)
+         {
+           CLAMP (out_r, 0, 1);
+           CLAMP (out_g, 0, 1);
+           CLAMP (out_b, 0, 1);
+         }
+         if (! (isfinite(out_r) && isfinite(out_g) &&
+                isfinite(out_b) && isfinite(out_a)))
+         {     // substitute NaN or unclipped infinity with user's color (RGB)
+           out_r = rgb_r;
+           out_g = rgb_g;
+           out_b = rgb_b;
+           out_a = key_a;
+         }
+         row[trk_out][0] = out_r;
+         row[trk_out][1] = out_g;
+         row[trk_out][2] = out_b;
+       }       // if color_work
+       if (! has_alpha)                // evtl simulate alpha channel
+       {
+         row[trk_out][0] *= out_a;
+         row[trk_out][1] *= out_a;
+         row[trk_out][2] *= out_a;
+       }                               // store real alpha channel
+       if (has_alpha) row[trk_out][3] = out_a;
+       for (l=0; l<layers; l++)        // increment all rows to next pixel
+       {
+         row[l] += 3;
+         if (has_alpha) row[l] ++;
+       }
+      }                // scan pixels for j = 0 .. width
+    }          // scan rows for i = y1 .. y2
+    break;
+
+  case BC_RGB888:              // RGB  [ 0 .. 1 ], must be in bounds
+  case BC_RGBA8888:            // RGBA [ 0 .. 1 ], must be in bounds
+    for (int i=y1; i<y2; i++)          // scan all rows
+    {
+      unsigned char *row[layers];
+      for (l=0; l<layers; l++)
+       row[l] = (unsigned char *)frame[l]->get_rows()[i];
+      for (int j=0; j<width; j++)      // scan all pixels
+      {
+       k = start;
+       for (l=0; l<layers; l++)        // convert source frames to args
+       {
+         if (color_work == BlendAlgebraConfig::YUV)
+         {
+           YUV::yuv.rgb_to_yuv_f ((float)row[l][0]/255,
+                                  (float)row[l][1]/255,
+                                  (float)row[l][2]/255,
+                                  r[k],        // Y pixel to blend
+                                  g[k],        // U pixel
+                                  b[k]);       // V pixel
+           yk = yuv_y;                         // user's key color (YUV)
+           uk = yuv_u;
+           vk = yuv_v;
+         }
+         else if (color_work == BlendAlgebraConfig::HSV)
+         {
+           HSV::rgb_to_hsv ((float)row[l][0]/255,
+                            (float)row[l][1]/255,
+                            (float)row[l][2]/255,
+                            r[k],              // H pixel to blend
+                            g[k],              // S pixel
+                            b[k]);             // V pixel
+           yk = hsv_h;                         // user's key color (HSV)
+           uk = hsv_s;
+           vk = hsv_v;
+         }
+         else  // either RGB or by PROJECT, conversion to float only
+         {
+           r[k] = (float)row[l][0]/255;        // RGB pixel to blend
+           g[k] = (float)row[l][1]/255;
+           b[k] = (float)row[l][2]/255;
+           yk   = rgb_r;                       // user's key color (RGB)
+           uk   = rgb_g;
+           vk   = rgb_b;
+         }     // if color_work
+         a[k] = has_alpha ? (float)row[l][3]/255 : 1;
+         if (config.clear_input)       // leave output track exclusively
+         {
+           row[l][0] = row[l][1] = row[l][2] = 0;
+           if (has_alpha) row[l][3] = 0;
+         }
+         k += step;
+       }       // scan tracks for l = 0 .. layers
+       out_r = r[arg_out];     // preinitialize args holding output results
+       out_g = g[arg_out];
+       out_b = b[arg_out];
+       out_a = a[arg_out];
+
+       // Call user function
+       curr_func.proc (layers, r, g, b, a, yk, uk, vk, key_a,
+                       &out_r, &out_g, &out_b, &out_a,
+                       j, i, width, height, has_alpha);
+
+       if (clip_colors) CLAMP (out_a, 0, 1);
+       if (color_work == BlendAlgebraConfig::YUV)
+       {
+         if (clip_colors)
+         {
+           CLAMP (out_r,  0,   1);
+           CLAMP (out_g, -0.5, 0.5);
+           CLAMP (out_b, -0.5, 0.5);
+         }
+         if (! (isfinite(out_r) && isfinite(out_g) &&
+                isfinite(out_b) && isfinite(out_a)))
+         {     // substitute NaN or unclipped infinity with user's color (YUV)
+           out_r = yuv_y;
+           out_g = yuv_u;
+           out_b = yuv_v;
+           out_a = key_a;
+         }
+         YUV::yuv.yuv_to_rgb_f (rk, gk, bk,
+                                out_r,         // Y
+                                out_g,         // U
+                                out_b);        // V
+       }
+       else if (color_work == BlendAlgebraConfig::HSV)
+       {
+         if (clip_colors)
+         {
+           if (isfinite(out_r) && (out_r < 0 || out_r >= 360))
+             out_r -= floor(out_r/360)*360;    // cannot clamp infinity here
+           CLAMP (out_g, 0, 1);
+           CLAMP (out_b, 0, 1);
+         }
+         if (! (isfinite(out_r) && isfinite(out_g) &&
+                isfinite(out_b) && isfinite(out_a)))
+         {     // substitute NaN or unclipped infinity with user's color (HSV)
+           out_r = hsv_h;
+           out_g = hsv_s;
+           out_b = hsv_v;
+           out_a = key_a;
+         }
+         HSV::hsv_to_rgb (rk, gk, bk,
+                          out_r,               // H
+                          out_g,               // S
+                          out_b);              // V
+       }
+       else    // either RGB or by PROJECT, no change, clip only
+       {
+         if (clip_colors)
+         {
+           CLAMP (out_r, 0, 1);
+           CLAMP (out_g, 0, 1);
+           CLAMP (out_b, 0, 1);
+         }
+         if (! (isfinite(out_r) && isfinite(out_g) &&
+                isfinite(out_b) && isfinite(out_a)))
+         {     // substitute NaN or unclipped infinity with user's color (RGB)
+           out_r = rgb_r;
+           out_g = rgb_g;
+           out_b = rgb_b;
+           out_a = key_a;
+         }
+         rk = out_r;
+         gk = out_g;
+         bk = out_b;
+       }       // if color_work
+       if (! has_alpha)                // evtl simulate alpha channel
+       {
+         rk *= out_a;
+         gk *= out_a;
+         bk *= out_a;
+       }
+       row[trk_out][0] = (unsigned char) CLIP (rk*255, 0, 255);//reformat/clip
+       row[trk_out][1] = (unsigned char) CLIP (gk*255, 0, 255);
+       row[trk_out][2] = (unsigned char) CLIP (bk*255, 0, 255);
+       if (has_alpha)                  // store real alpha channel
+         row[trk_out][3] = (unsigned char) CLIP (out_a*255, 0, 255);
+       for (l=0; l<layers; l++)        // increment all rows to next pixel
+       {
+         row[l] += 3;
+         if (has_alpha) row[l] ++;
+       }
+      }                // scan pixels for j = 0 .. width
+    }          // scan rows for i = y1 .. y2
+    break;
+
+  case BC_YUV888:      // R  [ 0 .. 1 ], GB [ -0.5 .. 0.5 ], must be in bounds
+  case BC_YUVA8888:    // RA [ 0 .. 1 ], GB [ -0.5 .. 0.5 ], must be in bounds
+    for (int i=y1; i<y2; i++)          // scan all rows
+    {
+      unsigned char *row[layers];
+      for (l=0; l<layers; l++)
+       row[l] = (unsigned char *)frame[l]->get_rows()[i];
+      for (int j=0; j<width; j++)      // scan all pixels
+      {
+       k = start;
+       for (l=0; l<layers; l++)        // convert source frames to args
+       {
+         if (color_work == BlendAlgebraConfig::RGB)
+         {
+           YUV::yuv.yuv_to_rgb_f (r[k], g[k], b[k], // RGB pixel to blend
+                                  (float)row[l][0]/255,
+                                  ((float)row[l][1]-128)/256,
+                                  ((float)row[l][2]-128)/256);
+           yk = rgb_r;                         // user's key color (RGB)
+           uk = rgb_g;
+           vk = rgb_b;
+         }
+         else if (color_work == BlendAlgebraConfig::HSV)
+         {
+           YUV::yuv.yuv_to_rgb_f (rk, gk, bk,  // RGB temporary pixel
+                                  (float)row[l][0]/255,
+                                  ((float)row[l][1]-128)/256,
+                                  ((float)row[l][2]-128)/256);
+           HSV::rgb_to_hsv (rk, gk, bk,
+                            r[k],              // H pixel to blend
+                            g[k],              // S pixel
+                            b[k]);             // V pixel
+           yk = hsv_h;                         // user's key color (HSV)
+           uk = hsv_s;
+           vk = hsv_v;
+         }
+         else  // either YUV or by PROJECT, conversion to float only
+         {
+           r[k] =  (float)row[l][0]/255;       // Y pixel to blend
+           g[k] = ((float)row[l][1]-128)/256;  // U pixel
+           b[k] = ((float)row[l][2]-128)/256;  // V pixel
+           yk = yuv_y;                         // user's key color (YUV)
+           uk = yuv_u;
+           vk = yuv_v;
+         }     // if color_work
+         a[k] = has_alpha ? (float)row[l][3]/255 : 1;
+         if (config.clear_input)       // leave output track exclusively
+         {
+           row[l][0] = row[l][1] = row[l][2] = 0;
+           if (has_alpha) row[l][3] = 0;
+         }
+         k += step;
+       }       // scan tracks for l = 0 .. layers
+       out_r = r[arg_out];     // preinitialize args holding output results
+       out_g = g[arg_out];
+       out_b = b[arg_out];
+       out_a = a[arg_out];
+
+       // Call user function
+       curr_func.proc (layers, r, g, b, a, yk, uk, vk, key_a,
+                       &out_r, &out_g, &out_b, &out_a,
+                       j, i, width, height, has_alpha);
+
+       if (clip_colors) CLAMP (out_a, 0, 1);
+       if (color_work == BlendAlgebraConfig::RGB)
+       {
+         if (clip_colors)
+         {
+           CLAMP (out_r, 0, 1);
+           CLAMP (out_g, 0, 1);
+           CLAMP (out_b, 0, 1);
+         }
+         if (! (isfinite(out_r) && isfinite(out_g) &&
+                isfinite(out_b) && isfinite(out_a)))
+         {     // substitute NaN or unclipped infinity with user's color (RGB)
+           out_r = rgb_r;
+           out_g = rgb_g;
+           out_b = rgb_b;
+           out_a = key_a;
+         }
+         YUV::yuv.rgb_to_yuv_f (out_r, out_g, out_b, yk, uk, vk);
+       }
+       else if (color_work == BlendAlgebraConfig::HSV)
+       {
+         if (clip_colors)
+         {
+           if (isfinite(out_r) && (out_r < 0 || out_r >= 360))
+             out_r -= floor(out_r/360)*360;    // cannot clamp infinity here
+           CLAMP (out_g, 0, 1);
+           CLAMP (out_b, 0, 1);
+         }
+         if (! (isfinite(out_r) && isfinite(out_g) &&
+                isfinite(out_b) && isfinite(out_a)))
+         {     // substitute NaN or unclipped infinity with user's color (HSV)
+           out_r = hsv_h;
+           out_g = hsv_s;
+           out_b = hsv_v;
+           out_a = key_a;
+         }                       //     H      S      V
+         HSV::hsv_to_rgb (rk, gk, bk, out_r, out_g, out_b);
+         if (clip_colors)
+         {
+           CLAMP (out_r, 0, 1);
+           CLAMP (out_g, 0, 1);
+           CLAMP (out_b, 0, 1);
+         }
+         YUV::yuv.rgb_to_yuv_f (rk, gk, bk, yk, uk, vk);
+       }
+       else    // either YUV or by PROJECT, no change, clip only
+       {
+         if (clip_colors)
+         {
+           CLAMP (out_r,  0,   1);
+           CLAMP (out_g, -0.5, 0.5);
+           CLAMP (out_b, -0.5, 0.5);
+         }
+         if (! (isfinite(out_r) && isfinite(out_g) &&
+                isfinite(out_b) && isfinite(out_a)))
+         {     // substitute NaN or unclipped infinity with user's color (YUV)
+           out_r = yuv_y;
+           out_g = yuv_u;
+           out_b = yuv_v;
+           out_a = key_a;
+         }
+         yk = out_r;
+         uk = out_g;
+         vk = out_b;
+       }       // if color_work
+       if (! has_alpha)                // evtl simulate alpha channel
+       {
+         yk *= out_a;
+         uk *= out_a;
+         vk *= out_a;
+       }
+       row[trk_out][0] = (unsigned char) CLIP (yk*255,       0, 255);
+       row[trk_out][1] = (unsigned char) CLIP ((uk+0.5)*256, 0, 255);
+       row[trk_out][2] = (unsigned char) CLIP ((vk+0.5)*256, 0, 255);
+       if (has_alpha)                  // store real alpha channel
+         row[trk_out][3] = (unsigned char) CLIP (out_a*255, 0, 255);
+       for (l=0; l<layers; l++)        // increment all rows to next pixel
+       {
+         row[l] += 3;
+         if (has_alpha) row[l] ++;
+       }
+      }                // scan pixels for j = 0 .. width
+    }          // scan rows for i = y1 .. y2
+    break;
+
+  default:
+    break;
+  }            // switch color_proj
+}                                              // WHEW !!!
+
+void BlendAlgebra::save_data(KeyFrame *keyframe)
+{
+  FileXML output;
+
+  output.set_shared_output(keyframe->xbuf);
+
+  output.tag.set_title("BLEND_ALGEBRA");
+  output.tag.set_property("FUNCNAME",     config.funcname);
+  output.tag.set_property("PARALLEL",     config.parallel);
+  output.tag.set_property("DIRECTION",    config.direction);
+  output.tag.set_property("OUTPUT_TRACK", config.output_track);
+  output.tag.set_property("COLORSPACE",   config.colorspace);
+  output.tag.set_property("CLIPCOLORS",   config.clipcolors);
+  output.tag.set_property("CLEAR_INPUT",  config.clear_input);
+  output.tag.set_property("RED",          config.red);
+  output.tag.set_property("GREEN",        config.green);
+  output.tag.set_property("BLUE",         config.blue);
+  output.tag.set_property("ALPHA",        config.alpha);
+  output.append_tag();
+  output.tag.set_title("/BLEND_ALGEBRA");
+  output.append_tag();
+  output.append_newline();
+  output.terminate_string();
+}
+
+void BlendAlgebra::read_data(KeyFrame *keyframe)
+{
+  FileXML input;
+
+  input.set_shared_input(keyframe->xbuf);
+
+  while(!input.read_tag())
+  {
+    if(input.tag.title_is("BLEND_ALGEBRA"))
+    {
+      input.tag.get_property("FUNCNAME", config.funcname);
+      config.parallel  = input.tag.get_property("PARALLEL",  config.parallel);
+      config.direction = input.tag.get_property("DIRECTION", config.direction);
+      config.output_track =
+       input.tag.get_property("OUTPUT_TRACK", config.output_track);
+      config.colorspace =
+       input.tag.get_property("COLORSPACE", config.colorspace);
+      config.clipcolors =
+       input.tag.get_property("CLIPCOLORS", config.clipcolors);
+      config.clear_input =
+       input.tag.get_property("CLEAR_INPUT", config.clear_input);
+      config.red   = input.tag.get_property("RED",   config.red);
+      config.green = input.tag.get_property("GREEN", config.green);
+      config.blue  = input.tag.get_property("BLUE",  config.blue);
+      config.alpha = input.tag.get_property("ALPHA", config.alpha);
+    }
+  }
+}
+
+void BlendAlgebra::update_gui()
+{
+  if( ! thread ) return;
+  if( ! (load_configuration() || inspect_configuration) ) return;
+  inspect_configuration = 0;   // update once after change or after creation
+  thread->window->lock_window("BlendAlgebra::update_gui");
+  BlendAlgebraWindow *window = (BlendAlgebraWindow*)thread->window;
+
+  window->funcname->update(config.funcname);
+  window->parallel->update(config.parallel);
+  window->clipcolors->update(config.clipcolors);
+  window->clear_input->update(config.clear_input);
+  window->direction->set_text(
+    BlendAlgebraConfig::direction_to_text(config.direction));
+  window->output->set_text(
+    BlendAlgebraConfig::output_to_text(config.output_track));
+  window->colorspace->set_text(
+    BlendAlgebraConfig::colorspace_to_text(config.colorspace));
+  window->update_key_sample();
+  window->alpha_text->update(config.alpha);
+  window->key_alpha->update(config.alpha);
+
+  thread->window->unlock_window();
+}
+
+////////////////////////////////////////////
+// Multithreaded processing stuff
+////////////////////////////////////////////
+
+BlendAlgebraEngine::BlendAlgebraEngine(BlendAlgebra *plugin, 
+                                      int total_clients, 
+                                      int total_packages)
+  : LoadServer(total_clients, total_packages)
+{
+  this->plugin = plugin;
+}
+
+void BlendAlgebraEngine::init_packages ()
+{
+  for (int i=0; i<get_total_packages(); i++)
+  {
+    BlendAlgebraPackage *pkg = (BlendAlgebraPackage *) get_package (i);
+    pkg->y1 = plugin->height *  i      / get_total_packages ();
+    pkg->y2 = plugin->height * (i + 1) / get_total_packages ();
+  }
+}
+
+LoadClient *BlendAlgebraEngine::new_client ()
+{
+  return new BlendAlgebraUnit (plugin, this);
+}
+
+LoadPackage *BlendAlgebraEngine::new_package ()
+{
+  return new BlendAlgebraPackage;
+}
+
+BlendAlgebraPackage::BlendAlgebraPackage()
+  : LoadPackage()
+{
+}
+
+BlendAlgebraUnit::BlendAlgebraUnit (BlendAlgebra *plugin,
+                                   BlendAlgebraEngine *engine)
+  : LoadClient (engine)
+{
+  this->plugin = plugin;
+  this->engine = engine;
+}
+
+void BlendAlgebraUnit::process_package(LoadPackage *package)
+{
+  BlendAlgebraPackage *pkg = (BlendAlgebraPackage *) package;
+
+  plugin->process_frames (pkg->y1, pkg->y2);
+}
diff --git a/cinelerra-5.1/plugins/blendalgebra/blendalgebra.h b/cinelerra-5.1/plugins/blendalgebra/blendalgebra.h
new file mode 100644 (file)
index 0000000..78b9054
--- /dev/null
@@ -0,0 +1,463 @@
+/*
+ * CINELERRA
+ * Copyright (C) 2008 Adam Williams <broadcast at earthling dot net>
+ *
+ * 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
+ *
+ */
+
+#ifndef BLENDALGEBRA_H
+#define BLENDALGEBRA_H
+
+#include "guicast.h"
+#include "loadbalance.h"
+#include "colorpicker.h"
+#include "pluginvclient.h"
+
+// Several forward declarations
+
+class BlendAlgebra;
+class BlendAlgebraConfig;
+class BlendAlgebraWindow;
+class BlendAlgebraAlphaText;
+class BlendAlgebraAlphaSlider;
+class BlendAlgebraFileBox;
+
+// Plugin configuration class definition
+
+class BlendAlgebraConfig
+{
+public:
+  BlendAlgebraConfig();
+
+  int equivalent(BlendAlgebraConfig &that);
+  void copy_from(BlendAlgebraConfig &that);
+  void interpolate(BlendAlgebraConfig &prev,
+                  BlendAlgebraConfig &next,
+                  int64_t prev_frame,
+                  int64_t next_frame,
+                  int64_t current_frame);
+
+  int get_key_color();
+
+  char funcname[BCTEXTLEN];
+  int parallel;
+  int clipcolors;
+  int clear_input;
+
+  static const char *direction_to_text(int direction);
+  int direction;
+  enum
+  {
+    BOTTOM_FIRST,
+    TOP_FIRST
+  };
+
+  static const char *output_to_text(int output_track);
+  int output_track;
+  enum
+  {
+    TOP,
+    BOTTOM
+  };
+
+  static const char *colorspace_to_text(int colorspace);
+  int colorspace;
+  enum
+  {
+    AUTO,                              // requested from function
+    RGB,
+    YUV,
+    HSV,
+    PROJECT                            // as defined in project settings
+  };
+
+  float red;                           // key color to substitute for NaN
+  float green;
+  float blue;
+  float alpha;                         // alpha to substitute for NaN
+};
+
+// Plugin dialog window class definition
+
+class BlendAlgebraFuncname : public BC_TextBox
+{
+public:
+  BlendAlgebraFuncname(BlendAlgebra *plugin, const char *funcname,
+                      BlendAlgebraWindow *gui, int x, int y);
+  int handle_event();
+  BlendAlgebra *plugin;
+  BlendAlgebraWindow *gui;
+};
+
+class BlendAlgebraDetach : public BC_GenericButton
+{
+public:
+  BlendAlgebraDetach(BlendAlgebra *plugin, BlendAlgebraWindow *gui,
+                    int x, int y);
+  int handle_event();
+  BlendAlgebra *plugin;
+  BlendAlgebraWindow *gui;
+};
+
+class BlendAlgebraEdit : public BC_GenericButton
+{
+public:
+  BlendAlgebraEdit(BlendAlgebra *plugin, BlendAlgebraWindow *gui,
+                  int x, int y);
+  int handle_event();
+  BlendAlgebra *plugin;
+  BlendAlgebraWindow *gui;
+};
+
+class BlendAlgebraRefresh : public BC_GenericButton
+{
+public:
+  BlendAlgebraRefresh(BlendAlgebra *plugin, BlendAlgebraWindow *gui,
+                     int x, int y);
+  int handle_event();
+  BlendAlgebra *plugin;
+  BlendAlgebraWindow *gui;
+};
+
+class BlendAlgebraToCurdir : public BC_GenericButton
+{
+public:
+  BlendAlgebraToCurdir(BlendAlgebraFileBox *file_box, int x, int y);
+  int handle_event();
+  BlendAlgebraFileBox *file_box;
+};
+
+class BlendAlgebraToUsrlib : public BC_GenericButton
+{
+public:
+  BlendAlgebraToUsrlib(BlendAlgebraFileBox *file_box, int x, int y);
+  int handle_event();
+  BlendAlgebraFileBox *file_box;
+};
+
+class BlendAlgebraToSyslib : public BC_GenericButton
+{
+public:
+  BlendAlgebraToSyslib(BlendAlgebraFileBox *file_box, int x, int y);
+  int handle_event();
+  BlendAlgebraFileBox *file_box;
+};
+
+class BlendAlgebraCopyCurdir : public BC_GenericButton
+{
+public:
+  BlendAlgebraCopyCurdir(BlendAlgebraFileBox *file_box, int x, int y);
+  int handle_event();
+  BlendAlgebraFileBox *file_box;
+};
+
+class BlendAlgebraCopyUsrlib : public BC_GenericButton
+{
+public:
+  BlendAlgebraCopyUsrlib(BlendAlgebraFileBox *file_box, int x, int y);
+  int handle_event();
+  BlendAlgebraFileBox *file_box;
+};
+
+class BlendAlgebraFileEdit : public BC_GenericButton
+{
+public:
+  BlendAlgebraFileEdit(BlendAlgebraFileBox *file_box, int x, int y);
+  int handle_event();
+  BlendAlgebraFileBox *file_box;
+};
+
+class BlendAlgebraFileBox : public BC_FileBox
+{
+public:
+  BlendAlgebraFileBox(BlendAlgebra *plugin, BlendAlgebraWindow *gui,
+                     char *init_path);
+  ~BlendAlgebraFileBox();
+  void add_objects();
+  int resize_event(int w, int h);
+
+  BlendAlgebra *plugin;
+  BlendAlgebraWindow *gui;
+  BlendAlgebraToCurdir *to_curdir;
+  BlendAlgebraToUsrlib *to_usrlib;
+  BlendAlgebraToSyslib *to_syslib;
+  BlendAlgebraCopyCurdir *copy_curdir;
+  BlendAlgebraCopyUsrlib *copy_usrlib;
+  BlendAlgebraFileEdit *file_edit;
+
+  int reinit_path;
+};
+
+class BlendAlgebraFileButton : public BC_GenericButton, public Thread
+{
+public:
+  BlendAlgebraFileButton(BlendAlgebra *plugin, BlendAlgebraWindow *gui,
+                        int x, int y);
+  ~BlendAlgebraFileButton();
+  int handle_event();
+  void run();
+  void stop();
+  BlendAlgebra *plugin;
+  BlendAlgebraWindow *gui;
+  BlendAlgebraFileBox *file_box;
+};
+
+class BlendAlgebraDirection : public BC_PopupMenu
+{
+public:
+  BlendAlgebraDirection(BlendAlgebra *plugin, int x, int y);
+  void create_objects();
+  int handle_event();
+  BlendAlgebra *plugin;
+};
+
+class BlendAlgebraOutput : public BC_PopupMenu
+{
+public:
+  BlendAlgebraOutput(BlendAlgebra *plugin, int x, int y);
+  void create_objects();
+  int handle_event();
+  BlendAlgebra *plugin;
+};
+
+class BlendAlgebraColorspace : public BC_PopupMenu
+{
+public:
+  BlendAlgebraColorspace(BlendAlgebra *plugin, int x, int y);
+  void create_objects();
+  int handle_event();
+  BlendAlgebra *plugin;
+};
+
+class BlendAlgebraClipcolors : public BC_CheckBox
+{
+public:
+  BlendAlgebraClipcolors(BlendAlgebra *plugin, BlendAlgebraWindow *gui,
+                        int x, int y);
+  int handle_event();
+  BlendAlgebra *plugin;
+  BlendAlgebraWindow *gui;
+};
+
+class BlendAlgebraParallel : public BC_CheckBox
+{
+public:
+  BlendAlgebraParallel(BlendAlgebra *plugin, BlendAlgebraWindow *gui,
+                      int x, int y);
+  int handle_event();
+  BlendAlgebra *plugin;
+  BlendAlgebraWindow *gui;
+};
+
+class BlendAlgebraClearInput : public BC_CheckBox
+{
+public:
+  BlendAlgebraClearInput(BlendAlgebra *plugin, BlendAlgebraWindow *gui,
+                        int x, int y);
+  int handle_event();
+  BlendAlgebra *plugin;
+  BlendAlgebraWindow *gui;
+};
+
+class BlendAlgebraKeyColor : public BC_GenericButton
+{
+public:
+  BlendAlgebraKeyColor(BlendAlgebra *plugin, BlendAlgebraWindow *gui,
+                      int x, int y);
+  int handle_event();
+  BlendAlgebra *plugin;
+  BlendAlgebraWindow *gui;
+};
+
+class BlendAlgebraColorPicker : public BC_GenericButton
+{
+public:
+  BlendAlgebraColorPicker(BlendAlgebra *plugin, BlendAlgebraWindow *gui,
+                         int x, int y);
+  int handle_event();
+  BlendAlgebra *plugin;
+  BlendAlgebraWindow *gui;
+};
+
+class BlendAlgebraColorThread : public ColorPicker
+{
+public:
+  BlendAlgebraColorThread(BlendAlgebra *plugin, BlendAlgebraWindow *gui);
+  int handle_new_color(int output, int alpha);
+  BlendAlgebra *plugin;
+  BlendAlgebraWindow *gui;
+};
+
+class BlendAlgebraAlphaText : public BC_TumbleTextBox
+{
+public:
+  BlendAlgebraAlphaText(BlendAlgebra *plugin, BlendAlgebraWindow *gui,
+                       BlendAlgebraAlphaSlider *slider, int x, int y,
+                       float min, float max, float *output);
+  ~BlendAlgebraAlphaText();
+  int handle_event();
+  BlendAlgebra *plugin;
+  BlendAlgebraWindow *gui;
+  BlendAlgebraAlphaSlider *slider;
+  float *output;
+  float min, max;
+};
+
+class BlendAlgebraAlphaSlider : public BC_FSlider
+{
+public:
+  BlendAlgebraAlphaSlider(BlendAlgebra *plugin, BlendAlgebraAlphaText *text,
+                         int x, int y, int w, float min, float max,
+                         float *output);
+  ~BlendAlgebraAlphaSlider();
+  int handle_event();
+  BlendAlgebra *plugin;
+  BlendAlgebraAlphaText *text;
+  float *output;
+};
+
+class BlendAlgebraWindow : public PluginClientWindow
+{
+public:
+  BlendAlgebraWindow(BlendAlgebra *plugin);
+  ~BlendAlgebraWindow();
+
+  void create_objects();
+  void update_key_sample();
+  void done_event();
+  int close_event();
+  int hide_window(int flush=1);
+
+  BlendAlgebra *plugin;
+  BlendAlgebraFuncname *funcname;
+  BlendAlgebraDirection *direction;
+  BlendAlgebraOutput *output;
+  BlendAlgebraColorspace *colorspace;
+  BlendAlgebraClipcolors *clipcolors;
+  BlendAlgebraParallel *parallel;
+  BlendAlgebraClearInput *clear_input;
+  BC_SubWindow *key_sample;
+  BlendAlgebraKeyColor *key_color;
+  BlendAlgebraColorPicker *color_picker;
+  BlendAlgebraColorThread *color_thread;
+  BlendAlgebraAlphaText *alpha_text;
+  BlendAlgebraAlphaSlider *key_alpha;
+  BlendAlgebraFileButton *file_button;
+  BlendAlgebraDetach *detach_button;
+  BlendAlgebraEdit *edit_button;
+  BlendAlgebraRefresh *refresh_button;
+
+  Mutex *editing_lock;
+  int editing;
+};
+
+// For multithreading processing engine
+
+class BlendAlgebraEngine : public LoadServer
+{
+public:
+  BlendAlgebraEngine(BlendAlgebra *plugin,
+                    int total_clients, int total_packages);
+  void init_packages();
+  LoadClient* new_client();
+  LoadPackage* new_package();
+
+  BlendAlgebra *plugin;
+};
+
+class BlendAlgebraPackage : public LoadPackage
+{
+public:
+  BlendAlgebraPackage();
+
+  int y1, y2;
+};
+
+class BlendAlgebraUnit : public LoadClient
+{
+public:
+  BlendAlgebraUnit(BlendAlgebra *plugin, BlendAlgebraEngine *engine);
+  void process_package(LoadPackage *package);
+
+  BlendAlgebra *plugin;
+  BlendAlgebraEngine *engine;
+};
+
+// Plugin main class definition
+
+// User function prototypes, C style, processing and init entries
+typedef void (*BAF_proc) (int,
+                         float *, float *, float *, float *,
+                         float,   float,   float,   float,
+                         float *, float *, float *, float *,
+                         int,     int,     int,     int, int);
+typedef void (*BAF_init) (int *, int, int *, int, int *, int, int, int, int);
+
+class BlendAlgebraFunc
+{
+public:
+  BlendAlgebraFunc();
+  ~BlendAlgebraFunc();
+
+  char src[BCTEXTLEN];                         // source filename
+  void *handle;                                        // handle returned from dlopen()
+  BAF_proc proc;                // main processing entry from dlsym()
+  BAF_init init;               // optional initializing entry from dlsym()
+  time_t tstamp;               // timestamp when function was last linked
+};
+
+class BlendAlgebra : public PluginVClient
+{
+public:
+  BlendAlgebra(PluginServer *server);
+  ~BlendAlgebra();
+
+  PLUGIN_CLASS_MEMBERS(BlendAlgebraConfig);
+
+  int process_buffer(VFrame **frame, int64_t start_position, double frame_rate);
+  int is_realtime();
+  int is_multichannel();
+  int is_synthesis();
+  void save_data(KeyFrame *keyframe);
+  void read_data(KeyFrame *keyframe);
+  void update_gui();
+  void process_frames(int y1, int y2);
+
+  // this flag set in constructor, cleared after first load_configuration()
+  int inspect_configuration;
+
+  int layers;                          // no of tracks
+  int width, height;                   // frame dimensions
+  int color_proj;                      // project color model
+  int color_work;                      // working color space in the function
+  int has_alpha;                       // 1 == has alpha channel
+
+  // color components in three color spaces, used to substitute
+  // for NaN or infinity in invalid results, or as a chroma key
+  float rgb_r, rgb_g, rgb_b, yuv_y, yuv_u, yuv_v, hsv_h, hsv_s, hsv_v, key_a;
+
+  BlendAlgebraFunc curr_func;                  // currently active entry point
+  int curr_func_no;                            // no of current entry in list
+  ArrayList<BlendAlgebraFunc*> funclist;       // list of known entry points
+
+  VFrame **frame;                              // pointer to frames to process
+
+  Mutex *func_lock;
+
+  BlendAlgebraEngine *engine;                  // for parallelized processing
+};
+
+#endif /* BLENDALGEBRA_H */
diff --git a/cinelerra-5.1/plugins/blendalgebra/graphart_burn.ba b/cinelerra-5.1/plugins/blendalgebra/graphart_burn.ba
new file mode 100644 (file)
index 0000000..bfc7df8
--- /dev/null
@@ -0,0 +1,29 @@
+/***********************************************-*-C-*-**********/
+/* Overlay mode: Graphic Art Burn */
+
+BLEND_ALGEBRA_INIT
+
+COLORSPACE_RGB
+PARALLEL_SAFE
+REQUIRE_TRACKS(2)
+
+BLEND_ALGEBRA_PROC
+
+#define s 1
+#define d 0
+
+R_OUT = R(s) * (1-A(d)) + R(d) * (1-A(s));
+if (R(s) > 0 && R(s) * A(d) + R(d) * A(s) > A(s) * A(d))
+  R_OUT += (R(s) * A(d) + R(d) * A(s) - A(s) * A(d)) * A(s) / R(s);
+
+G_OUT = G(s) * (1-A(d)) + G(d) * (1-A(s));
+if (G(s) > 0 && G(s) * A(d) + G(d) * A(s) > A(s) * A(d))
+  G_OUT += (G(s) * A(d) + G(d) * A(s) - A(s) * A(d)) * A(s) / G(s);
+
+B_OUT = B(s) * (1-A(d)) + B(d) * (1-A(s));
+if (B(s) > 0 && B(s) * A(d) + B(d) * A(s) > A(s) * A(d))
+  B_OUT += (B(s) * A(d) + B(d) * A(s) - A(s) * A(d)) * A(s) / B(s);
+
+A_OUT = A(s) + A(d) - A(s) * A(d);
+  
+BLEND_ALGEBRA_END
diff --git a/cinelerra-5.1/plugins/blendalgebra/graphart_difference.ba b/cinelerra-5.1/plugins/blendalgebra/graphart_difference.ba
new file mode 100644 (file)
index 0000000..2c678e2
--- /dev/null
@@ -0,0 +1,20 @@
+/***********************************************-*-C-*-**********/
+/* Overlay mode: Graphic Art Difference */
+
+BLEND_ALGEBRA_INIT
+
+COLORSPACE_RGB
+PARALLEL_SAFE
+REQUIRE_TRACKS(2)
+
+BLEND_ALGEBRA_PROC
+
+#define s 1
+#define d 0
+
+R_OUT = R(s) * (1-A(d)) + R(d) * (1-A(s)) + ABS (R(s) * A(d) - R(d) * A(s));
+G_OUT = G(s) * (1-A(d)) + G(d) * (1-A(s)) + ABS (G(s) * A(d) - G(d) * A(s));
+B_OUT = B(s) * (1-A(d)) + B(d) * (1-A(s)) + ABS (B(s) * A(d) - B(d) * A(s));
+A_OUT = A(s) + A(d) - A(s) * A(d);
+  
+BLEND_ALGEBRA_END
diff --git a/cinelerra-5.1/plugins/blendalgebra/graphart_dodge.ba b/cinelerra-5.1/plugins/blendalgebra/graphart_dodge.ba
new file mode 100644 (file)
index 0000000..2dfe519
--- /dev/null
@@ -0,0 +1,35 @@
+/***********************************************-*-C-*-**********/
+/* Overlay mode: Graphic Art Dodge */
+
+BLEND_ALGEBRA_INIT
+
+COLORSPACE_RGB
+PARALLEL_SAFE
+REQUIRE_TRACKS(2)
+
+BLEND_ALGEBRA_PROC
+
+#define s 1
+#define d 0
+
+R_OUT = R(s) * (1-A(d)) + R(d) * (1-A(s));
+if (A(s) <= R(s) || R(s) * A(d) + R(d) * A(s) >= A(s) * A(d))
+  R_OUT += A(s) * A(d);
+else
+  R_OUT += R(d) * A(s) / (1 - R(s)/A(s));
+
+G_OUT = G(s) * (1-A(d)) + G(d) * (1-A(s));
+if (A(s) <= G(s) || G(s) * A(d) + G(d) * A(s) >= A(s) * A(d))
+  G_OUT += A(s) * A(d);
+else
+  G_OUT += G(d) * A(s) / (1 - G(s)/A(s));
+
+B_OUT = B(s) * (1-A(d)) + B(d) * (1-A(s));
+if (A(s) <= B(s) || B(s) * A(d) + B(d) * A(s) >= A(s) * A(d))
+  B_OUT += A(s) * A(d);
+else
+  B_OUT += B(d) * A(s) / (1 - B(s)/A(s));
+
+A_OUT = A(s) + A(d) - A(s) * A(d);
+  
+BLEND_ALGEBRA_END
diff --git a/cinelerra-5.1/plugins/blendalgebra/graphart_hardlight.ba b/cinelerra-5.1/plugins/blendalgebra/graphart_hardlight.ba
new file mode 100644 (file)
index 0000000..c0cabbb
--- /dev/null
@@ -0,0 +1,29 @@
+/***********************************************-*-C-*-**********/
+/* Overlay mode: Graphic Art Hardlight */
+
+BLEND_ALGEBRA_INIT
+
+COLORSPACE_RGB
+PARALLEL_SAFE
+REQUIRE_TRACKS(2)
+
+BLEND_ALGEBRA_PROC
+
+#define s 1
+#define d 0
+
+R_OUT = R(s) * (1-A(d)) + R(d) * (1-A(s));
+if (2 * R(s) < A(s)) R_OUT += 2 * R(s) * R(d);
+else R_OUT += A(s) * A(d) - 2 * (A(d)-R(d)) * (A(s)-R(s));
+
+G_OUT = G(s) * (1-A(d)) + G(d) * (1-A(s));
+if (2 * G(s) < A(s)) G_OUT += 2 * G(s) * G(d);
+else G_OUT += A(s) * A(d) - 2 * (A(d)-G(d)) * (A(s)-G(s));
+
+B_OUT = B(s) * (1-A(d)) + B(d) * (1-A(s));
+if (2 * B(s) < A(s)) B_OUT += 2 * B(s) * B(d);
+else B_OUT += A(s) * A(d) - 2 * (A(d)-B(d)) * (A(s)-B(s));
+
+A_OUT = A(s) + A(d) - A(s) * A(d);
+  
+BLEND_ALGEBRA_END
diff --git a/cinelerra-5.1/plugins/blendalgebra/graphart_overlay.ba b/cinelerra-5.1/plugins/blendalgebra/graphart_overlay.ba
new file mode 100644 (file)
index 0000000..4228f22
--- /dev/null
@@ -0,0 +1,29 @@
+/***********************************************-*-C-*-**********/
+/* Overlay mode: Graphic Art Overlay */
+
+BLEND_ALGEBRA_INIT
+
+COLORSPACE_RGB
+PARALLEL_SAFE
+REQUIRE_TRACKS(2)
+
+BLEND_ALGEBRA_PROC
+
+#define s 1
+#define d 0
+
+R_OUT = R(s) * (1-A(d)) + R(d) * (1-A(s));
+if (2 * R(d) < A(d)) R_OUT += 2 * R(s) * R(d);
+else R_OUT += A(s) * A(d) - 2 * (A(d)-R(d)) * (A(s)-R(s));
+
+G_OUT = G(s) * (1-A(d)) + G(d) * (1-A(s));
+if (2 * G(d) < A(d)) G_OUT += 2 * G(s) * G(d);
+else G_OUT += A(s) * A(d) - 2 * (A(d)-G(d)) * (A(s)-G(s));
+
+B_OUT = B(s) * (1-A(d)) + B(d) * (1-A(s));
+if (2 * B(d) < A(d)) B_OUT += 2 * B(s) * B(d);
+else B_OUT += A(s) * A(d) - 2 * (A(d)-B(d)) * (A(s)-B(s));
+
+A_OUT = A(s) + A(d) - A(s) * A(d);
+  
+BLEND_ALGEBRA_END
diff --git a/cinelerra-5.1/plugins/blendalgebra/graphart_screen.ba b/cinelerra-5.1/plugins/blendalgebra/graphart_screen.ba
new file mode 100644 (file)
index 0000000..86cba9d
--- /dev/null
@@ -0,0 +1,20 @@
+/***********************************************-*-C-*-**********/
+/* Overlay mode: Graphic Art Screen */
+
+BLEND_ALGEBRA_INIT
+
+COLORSPACE_RGB
+PARALLEL_SAFE
+REQUIRE_TRACKS(2)
+
+BLEND_ALGEBRA_PROC
+
+#define s 1
+#define d 0
+
+R_OUT = R(s) + R(d) - R(s) * R(d);
+G_OUT = G(s) + G(d) - G(s) * G(d);
+B_OUT = B(s) + B(d) - B(s) * B(d);
+A_OUT = A(s) + A(d) - A(s) * A(d);
+  
+BLEND_ALGEBRA_END
diff --git a/cinelerra-5.1/plugins/blendalgebra/graphart_softlight.ba b/cinelerra-5.1/plugins/blendalgebra/graphart_softlight.ba
new file mode 100644 (file)
index 0000000..00aea82
--- /dev/null
@@ -0,0 +1,26 @@
+/***********************************************-*-C-*-**********/
+/* Overlay mode: Graphic Art Softlight */
+
+BLEND_ALGEBRA_INIT
+
+COLORSPACE_RGB
+PARALLEL_SAFE
+REQUIRE_TRACKS(2)
+
+BLEND_ALGEBRA_PROC
+
+#define s 1
+#define d 0
+
+R_OUT = R(s) * (1-A(d)) + R(d) * (1-A(s));
+if (A(d) > 0) R_OUT += (R(d) * A(s) + 2 * R(s) * (A(d)-R(d))) / A(d);
+
+G_OUT = G(s) * (1-A(d)) + G(d) * (1-A(s));
+if (A(d) > 0) G_OUT += (G(d) * A(s) + 2 * G(s) * (A(d)-G(d))) / A(d);
+
+B_OUT = B(s) * (1-A(d)) + B(d) * (1-A(s));
+if (A(d) > 0) B_OUT += (B(d) * A(s) + 2 * B(s) * (A(d)-B(d))) / A(d);
+
+A_OUT = A(s) + A(d) - A(s) * A(d);
+  
+BLEND_ALGEBRA_END
diff --git a/cinelerra-5.1/plugins/blendalgebra/logical_and.ba b/cinelerra-5.1/plugins/blendalgebra/logical_and.ba
new file mode 100644 (file)
index 0000000..71fb662
--- /dev/null
@@ -0,0 +1,23 @@
+/***********************************************-*-C-*-**********/
+/* Overlay mode: Logical And, for any number of tracks */
+
+BLEND_ALGEBRA_INIT
+
+COLORSPACE_RGB
+PARALLEL_SAFE
+
+BLEND_ALGEBRA_PROC
+
+int i;
+
+R_OUT = G_OUT = B_OUT = A_OUT = 1;
+
+for (i=0; i<TOTAL_TRACKS; i++)
+{
+  R_OUT *= R(i);
+  G_OUT *= G(i);
+  B_OUT *= B(i);
+  A_OUT *= A(i);
+}
+  
+BLEND_ALGEBRA_END
diff --git a/cinelerra-5.1/plugins/blendalgebra/logical_darken.ba b/cinelerra-5.1/plugins/blendalgebra/logical_darken.ba
new file mode 100644 (file)
index 0000000..5d9f492
--- /dev/null
@@ -0,0 +1,20 @@
+/***********************************************-*-C-*-**********/
+/* Overlay mode: Logical Darken */
+
+BLEND_ALGEBRA_INIT
+
+COLORSPACE_RGB
+PARALLEL_SAFE
+REQUIRE_TRACKS(2)
+
+BLEND_ALGEBRA_PROC
+
+#define s 1
+#define d 0
+
+R_OUT = R(s) * (1-A(d)) + R(d) * (1-A(s)) + MIN (R(s)*A(d), R(d)*A(s));
+G_OUT = G(s) * (1-A(d)) + G(d) * (1-A(s)) + MIN (G(s)*A(d), G(d)*A(s));
+B_OUT = B(s) * (1-A(d)) + B(d) * (1-A(s)) + MIN (B(s)*A(d), B(d)*A(s));
+A_OUT = A(s) + A(d) - A(s) * A(d);
+  
+BLEND_ALGEBRA_END
diff --git a/cinelerra-5.1/plugins/blendalgebra/logical_lighten.ba b/cinelerra-5.1/plugins/blendalgebra/logical_lighten.ba
new file mode 100644 (file)
index 0000000..1aaa04f
--- /dev/null
@@ -0,0 +1,20 @@
+/***********************************************-*-C-*-**********/
+/* Overlay mode: Logical Lighten */
+
+BLEND_ALGEBRA_INIT
+
+COLORSPACE_RGB
+PARALLEL_SAFE
+REQUIRE_TRACKS(2)
+
+BLEND_ALGEBRA_PROC
+
+#define s 1
+#define d 0
+
+R_OUT = R(s) * (1-A(d)) + R(d) * (1-A(s)) + MAX (R(s)*A(d), R(d)*A(s));
+G_OUT = G(s) * (1-A(d)) + G(d) * (1-A(s)) + MAX (G(s)*A(d), G(d)*A(s));
+B_OUT = B(s) * (1-A(d)) + B(d) * (1-A(s)) + MAX (B(s)*A(d), B(d)*A(s));
+A_OUT = A(s) + A(d) - A(s) * A(d);
+  
+BLEND_ALGEBRA_END
diff --git a/cinelerra-5.1/plugins/blendalgebra/logical_max.ba b/cinelerra-5.1/plugins/blendalgebra/logical_max.ba
new file mode 100644 (file)
index 0000000..9df8bb8
--- /dev/null
@@ -0,0 +1,20 @@
+/***********************************************-*-C-*-**********/
+/* Overlay mode: Logical Max */
+
+BLEND_ALGEBRA_INIT
+
+COLORSPACE_RGB
+PARALLEL_SAFE
+REQUIRE_TRACKS(2)
+
+BLEND_ALGEBRA_PROC
+
+#define s 1
+#define d 0
+
+R_OUT = MAX (R(s), R(d));
+G_OUT = MAX (G(s), G(d));
+B_OUT = MAX (B(s), B(d));
+A_OUT = MAX (A(s), A(d));
+
+BLEND_ALGEBRA_END
diff --git a/cinelerra-5.1/plugins/blendalgebra/logical_min.ba b/cinelerra-5.1/plugins/blendalgebra/logical_min.ba
new file mode 100644 (file)
index 0000000..2273f97
--- /dev/null
@@ -0,0 +1,20 @@
+/***********************************************-*-C-*-**********/
+/* Overlay mode: Logical Min */
+
+BLEND_ALGEBRA_INIT
+
+COLORSPACE_RGB
+PARALLEL_SAFE
+REQUIRE_TRACKS(2)
+
+BLEND_ALGEBRA_PROC
+
+#define s 1
+#define d 0
+
+R_OUT = MIN (R(s), R(d));
+G_OUT = MIN (G(s), G(d));
+B_OUT = MIN (B(s), B(d));
+A_OUT = MIN (A(s), A(d));
+
+BLEND_ALGEBRA_END
diff --git a/cinelerra-5.1/plugins/blendalgebra/logical_or.ba b/cinelerra-5.1/plugins/blendalgebra/logical_or.ba
new file mode 100644 (file)
index 0000000..ce63133
--- /dev/null
@@ -0,0 +1,20 @@
+/***********************************************-*-C-*-**********/
+/* Overlay mode: Logical Or */
+
+BLEND_ALGEBRA_INIT
+
+COLORSPACE_RGB
+PARALLEL_SAFE
+REQUIRE_TRACKS(2)
+
+BLEND_ALGEBRA_PROC
+
+#define s 1
+#define d 0
+
+R_OUT = R(s) + R(d) - R(s) * R(d);
+G_OUT = G(s) + G(d) - G(s) * G(d);
+B_OUT = B(s) + B(d) - B(s) * B(d);
+A_OUT = A(s) + A(d) - A(s) * A(d);
+  
+BLEND_ALGEBRA_END
diff --git a/cinelerra-5.1/plugins/blendalgebra/logical_xor.ba b/cinelerra-5.1/plugins/blendalgebra/logical_xor.ba
new file mode 100644 (file)
index 0000000..af714e8
--- /dev/null
@@ -0,0 +1,22 @@
+/***********************************************-*-C-*-**********/
+/* Overlay mode: Logical Xor */
+
+BLEND_ALGEBRA_INIT
+
+COLORSPACE_RGB
+PARALLEL_SAFE
+REQUIRE_TRACKS(2)
+
+BLEND_ALGEBRA_PROC
+
+#define s 1
+#define d 0
+
+R_OUT = R(s) * (1-A(d)) + R(d) * (1-A(s));
+G_OUT = G(s) * (1-A(d)) + G(d) * (1-A(s));
+B_OUT = B(s) * (1-A(d)) + B(d) * (1-A(s));
+
+if (HAS_ALPHA) A_OUT = A(s) + A(d) - 2 * A(s) * A(d);
+else A_OUT = 1;
+
+BLEND_ALGEBRA_END
diff --git a/cinelerra-5.1/plugins/blendalgebra/ovl_normal.ba b/cinelerra-5.1/plugins/blendalgebra/ovl_normal.ba
new file mode 100644 (file)
index 0000000..170dbb1
--- /dev/null
@@ -0,0 +1,20 @@
+/***********************************************-*-C-*-**********/
+/* Overlay mode: Normal */
+
+BLEND_ALGEBRA_INIT
+
+COLORSPACE_RGB
+PARALLEL_SAFE
+REQUIRE_TRACKS(2)
+
+BLEND_ALGEBRA_PROC
+
+#define s 1
+#define d 0
+
+R_OUT = R(s) * A(s) + R(d) * (1-A(s));
+G_OUT = G(s) * A(s) + G(d) * (1-A(s));
+B_OUT = B(s) * A(s) + B(d) * (1-A(s));
+A_OUT = A(s) + A(d) * (1-A(s));
+  
+BLEND_ALGEBRA_END
diff --git a/cinelerra-5.1/plugins/blendalgebra/porterduff_dst.ba b/cinelerra-5.1/plugins/blendalgebra/porterduff_dst.ba
new file mode 100644 (file)
index 0000000..1812f55
--- /dev/null
@@ -0,0 +1,16 @@
+/***********************************************-*-C-*-**********/
+/* Overlay mode: Porter-Duff Dst */
+
+BLEND_ALGEBRA_INIT
+
+COLORSPACE_RGB
+PARALLEL_SAFE
+
+BLEND_ALGEBRA_PROC
+
+R_OUT = R(0);
+G_OUT = G(0);
+B_OUT = B(0);
+A_OUT = A(0);
+  
+BLEND_ALGEBRA_END
diff --git a/cinelerra-5.1/plugins/blendalgebra/porterduff_dstatop.ba b/cinelerra-5.1/plugins/blendalgebra/porterduff_dstatop.ba
new file mode 100644 (file)
index 0000000..512daee
--- /dev/null
@@ -0,0 +1,20 @@
+/***********************************************-*-C-*-**********/
+/* Overlay mode: Porter-Duff Dst Atop */
+
+BLEND_ALGEBRA_INIT
+
+COLORSPACE_RGB
+PARALLEL_SAFE
+REQUIRE_TRACKS(2)
+
+BLEND_ALGEBRA_PROC
+
+#define s 1
+#define d 0
+
+R_OUT = R(s) * (1-A(d)) + R(d) * A(s);
+G_OUT = G(s) * (1-A(d)) + G(d) * A(s);
+B_OUT = B(s) * (1-A(d)) + B(d) * A(s);
+A_OUT = A(s);
+  
+BLEND_ALGEBRA_END
diff --git a/cinelerra-5.1/plugins/blendalgebra/porterduff_dstin.ba b/cinelerra-5.1/plugins/blendalgebra/porterduff_dstin.ba
new file mode 100644 (file)
index 0000000..25ddf67
--- /dev/null
@@ -0,0 +1,20 @@
+/***********************************************-*-C-*-**********/
+/* Overlay mode: Porter-Duff Dst In */
+
+BLEND_ALGEBRA_INIT
+
+COLORSPACE_RGB
+PARALLEL_SAFE
+REQUIRE_TRACKS(2)
+
+BLEND_ALGEBRA_PROC
+
+#define s 1
+#define d 0
+
+R_OUT = R(d) * A(s);
+G_OUT = G(d) * A(s);
+B_OUT = B(d) * A(s);
+A_OUT = A(d) * A(s);
+  
+BLEND_ALGEBRA_END
diff --git a/cinelerra-5.1/plugins/blendalgebra/porterduff_dstout.ba b/cinelerra-5.1/plugins/blendalgebra/porterduff_dstout.ba
new file mode 100644 (file)
index 0000000..52a5252
--- /dev/null
@@ -0,0 +1,22 @@
+/***********************************************-*-C-*-**********/
+/* Overlay mode: Porter-Duff Dst Out */
+
+BLEND_ALGEBRA_INIT
+
+COLORSPACE_RGB
+PARALLEL_SAFE
+REQUIRE_TRACKS(2)
+
+BLEND_ALGEBRA_PROC
+
+#define s 1
+#define d 0
+
+R_OUT = R(d) * (1-A(s));
+G_OUT = G(d) * (1-A(s));
+B_OUT = B(d) * (1-A(s));
+
+if (HAS_ALPHA) A_OUT = A(d) * (1-A(s));
+else A_OUT = 1;
+
+BLEND_ALGEBRA_END
diff --git a/cinelerra-5.1/plugins/blendalgebra/porterduff_dstover.ba b/cinelerra-5.1/plugins/blendalgebra/porterduff_dstover.ba
new file mode 100644 (file)
index 0000000..e12e391
--- /dev/null
@@ -0,0 +1,20 @@
+/***********************************************-*-C-*-**********/
+/* Overlay mode: Porter-Duff Dst Over */
+
+BLEND_ALGEBRA_INIT
+
+COLORSPACE_RGB
+PARALLEL_SAFE
+REQUIRE_TRACKS(2)
+
+BLEND_ALGEBRA_PROC
+
+#define s 1
+#define d 0
+
+R_OUT = R(s) * (1-A(d)) + R(d);
+G_OUT = G(s) * (1-A(d)) + G(d);
+B_OUT = B(s) * (1-A(d)) + B(d);
+A_OUT = A(s) + A(d) - A(s) * A(d);
+  
+BLEND_ALGEBRA_END
diff --git a/cinelerra-5.1/plugins/blendalgebra/porterduff_src.ba b/cinelerra-5.1/plugins/blendalgebra/porterduff_src.ba
new file mode 100644 (file)
index 0000000..3ecd4ee
--- /dev/null
@@ -0,0 +1,17 @@
+/***********************************************-*-C-*-**********/
+/* Overlay mode: Porter-Duff Src */
+
+BLEND_ALGEBRA_INIT
+
+COLORSPACE_RGB
+PARALLEL_SAFE
+REQUIRE_TRACKS(2)
+
+BLEND_ALGEBRA_PROC
+
+R_OUT = R(1);
+G_OUT = G(1);
+B_OUT = B(1);
+A_OUT = A(1);
+  
+BLEND_ALGEBRA_END
diff --git a/cinelerra-5.1/plugins/blendalgebra/porterduff_srcatop.ba b/cinelerra-5.1/plugins/blendalgebra/porterduff_srcatop.ba
new file mode 100644 (file)
index 0000000..2bd5009
--- /dev/null
@@ -0,0 +1,20 @@
+/***********************************************-*-C-*-**********/
+/* Overlay mode: Porter-Duff Src Atop */
+
+BLEND_ALGEBRA_INIT
+
+COLORSPACE_RGB
+PARALLEL_SAFE
+REQUIRE_TRACKS(2)
+
+BLEND_ALGEBRA_PROC
+
+#define s 1
+#define d 0
+
+R_OUT = R(s) * A(d) + R(d) * (1-A(s));
+G_OUT = G(s) * A(d) + G(d) * (1-A(s));
+B_OUT = B(s) * A(d) + B(d) * (1-A(s));
+A_OUT = A(d);
+  
+BLEND_ALGEBRA_END
diff --git a/cinelerra-5.1/plugins/blendalgebra/porterduff_srcin.ba b/cinelerra-5.1/plugins/blendalgebra/porterduff_srcin.ba
new file mode 100644 (file)
index 0000000..b5312d1
--- /dev/null
@@ -0,0 +1,20 @@
+/***********************************************-*-C-*-**********/
+/* Overlay mode: Porter-Duff Src In */
+
+BLEND_ALGEBRA_INIT
+
+COLORSPACE_RGB
+PARALLEL_SAFE
+REQUIRE_TRACKS(2)
+
+BLEND_ALGEBRA_PROC
+
+#define s 1
+#define d 0
+
+R_OUT = R(s) * A(d);
+G_OUT = G(s) * A(d);
+B_OUT = B(s) * A(d);
+A_OUT = A(s) * A(d);
+  
+BLEND_ALGEBRA_END
diff --git a/cinelerra-5.1/plugins/blendalgebra/porterduff_srcout.ba b/cinelerra-5.1/plugins/blendalgebra/porterduff_srcout.ba
new file mode 100644 (file)
index 0000000..2bfc5f7
--- /dev/null
@@ -0,0 +1,22 @@
+/***********************************************-*-C-*-**********/
+/* Overlay mode: Porter-Duff Src Out */
+
+BLEND_ALGEBRA_INIT
+
+COLORSPACE_RGB
+PARALLEL_SAFE
+REQUIRE_TRACKS(2)
+
+BLEND_ALGEBRA_PROC
+
+#define s 1
+#define d 0
+
+R_OUT = R(s) * (1-A(d));
+G_OUT = G(s) * (1-A(d));
+B_OUT = B(s) * (1-A(d));
+
+if (HAS_ALPHA) A_OUT = A(s) * (1-A(d));
+else A_OUT = 1;
+
+BLEND_ALGEBRA_END
diff --git a/cinelerra-5.1/plugins/blendalgebra/porterduff_srcover.ba b/cinelerra-5.1/plugins/blendalgebra/porterduff_srcover.ba
new file mode 100644 (file)
index 0000000..154b926
--- /dev/null
@@ -0,0 +1,20 @@
+/***********************************************-*-C-*-**********/
+/* Overlay mode: Porter-Duff Src Over */
+
+BLEND_ALGEBRA_INIT
+
+COLORSPACE_RGB
+PARALLEL_SAFE
+REQUIRE_TRACKS(2)
+
+BLEND_ALGEBRA_PROC
+
+#define s 1
+#define d 0
+
+R_OUT = R(s) + (1-A(s)) * R(d);
+G_OUT = G(s) + (1-A(s)) * G(d);
+B_OUT = B(s) + (1-A(s)) * B(d);
+A_OUT = A(s) + A(d) - A(s) * A(d);
+  
+BLEND_ALGEBRA_END
diff --git a/cinelerra-5.1/plugins/blendalgebra/ydiff.ba b/cinelerra-5.1/plugins/blendalgebra/ydiff.ba
new file mode 100644 (file)
index 0000000..4f650db
--- /dev/null
@@ -0,0 +1,42 @@
+/***********************************************-*-C-*-***********/
+/* Cannot make printout in parallel due to static accumulators ! */
+/* Define NO_PRINTOUT to run parallelized but without printout   */
+/* Comment out NO_PRINTOUT to get printout and run sequentially  */
+
+/* #define NO_PRINTOUT */
+
+#ifndef NO_PRINTOUT
+static int npix=0;
+static float sum=0, sabs=0;
+#endif
+
+BLEND_ALGEBRA_INIT
+
+COLORSPACE_YUV
+REQUIRE_TRACKS(2)
+
+#ifdef NO_PRINTOUT
+PARALLEL_SAFE
+#else
+printf ("sz=%8d   err=%10g   abs=%10g", npix, sum, sabs);
+if (npix > 0) printf ("   rel=%10g", sabs*256/npix);
+printf ("\n");
+sum = sabs = 0;
+npix = 0;
+#endif
+
+BLEND_ALGEBRA_PROC
+
+#ifndef NO_PRINTOUT
+float diff;
+diff = Y(0)-Y(1);
+sum += diff;
+sabs += ABS (diff);
+npix ++;
+#endif
+
+Y_OUT = (Y(0)-Y(1))/2*exp((KEY_A-0.5)*14*M_LN2)+0.5;
+U_OUT = V_OUT = 0;
+A_OUT = 1;
+
+BLEND_ALGEBRA_END
diff --git a/cinelerra-5.1/plugins/blendprogram/BlendProgramCompile.pl b/cinelerra-5.1/plugins/blendprogram/BlendProgramCompile.pl
new file mode 100755 (executable)
index 0000000..5e3646d
--- /dev/null
@@ -0,0 +1,289 @@
+#!/usr/bin/perl
+
+# Helper script to compile Cinelerra blend programs
+# Calling: BlendProgramCompile.pl [options] <program filename>
+# The special option "-API" shows the numeric version of the script itself
+
+# Several important definitions
+
+# BlendProgramCompile.pl script API version. Must not be changed !
+$cin_bp_api = 1;
+
+# C compiler executable and options, can be redefined on user's demand
+$cin_compiler = $ENV{'CIN_CC'};
+$cin_compiler = $ENV{'CC'} if $cin_compiler eq '';
+# a likely default compiler
+$cin_compiler = 'gcc' if $cin_compiler eq '';
+# another possible compiler
+#$cin_compiler = 'clang' if $cin_compiler eq '';
+# a fake compiler for debugging the script itself
+#$cin_compiler = 'echo';
+
+# Mandatory compiler options to build dynamically loadable object, don't change
+$cin_compopts = '-fPIC';
+$cin_ldopts = '-shared';
+$cin_ldlibs = '-lm';
+
+# Adjustable compiler options for optimization, debugging, warnings
+$cin_optopts = '-O2';
+$cin_debopts = '-g';
+$cin_warnopts = '-Wall -W -Wno-unused-parameter';
+# more possible compiler options
+#$cin_optopts = '-Ofast -march=native';
+#$cin_debopts = '-ggdb';
+
+# Set these to 1 if want to optimize, debug, or verbose output by default
+$opt_opt  = 1;
+$opt_deb  = 0;
+$opt_verb = 0;
+
+# Text editor executable, can be redefined on user's preferences
+$cin_editor = $ENV{'CIN_EDITOR'};
+# a likely default editor
+$cin_editor = 'emacs' if $cin_editor eq '';
+# another possible editors
+#$cin_editor = 'konsole -e vim' if $cin_editor eq '';
+#$cin_editor = 'xterm -e vi' if $cin_editor eq '';
+#$cin_editor = 'xedit' if $cin_editor eq '';
+
+# Background execution; set it empty to execute in foreground and block
+$cin_bgr = ' &';
+
+# Nothing to change below this line
+
+# Cinelerra installation path
+$cin_dat = $ENV{'CIN_DAT'};
+
+# Cinelerra user's config directory
+$cin_config = $ENV{'CIN_CONFIG'};
+$cin_config = $ENV{'HOME'}.'/.bcast5'
+  if $cin_config eq '' && $ENV{'HOME'} ne '';
+$cin_config = '.' if $cin_config eq '';
+$me_config = "$cin_config/BlendProgramCompile.pl";
+
+sub Usage
+{
+  print "\nCinelerraGG blend program compiler driver usage\n\n";
+  print "$0 [options] <bp> compile blend program <bp>\n";
+  print "$0 -edit <bp>     open blend program <bp> in editor\n";
+  print "$0 -API           output own API version\n";
+  print "$0 -h, -help, -?  output this help text\n";
+  print "\nCinelerraGG additional blend program compiler options\n\n";
+  print "-compile   don't check modification time, compile unconditionally\n";
+  print "-cfile     don't remove intermediate .c file\n";
+  print "-opt       add optimizing options to compiler command line\n";
+  print "-debug     add debugging options to compiler command line\n";
+  print "-warn      add warning options to compiler command line\n";
+  print "-edit      open blend program source in text editor\n";
+  print "-verbose   verbose execution";
+  print " (active by default)" if $opt_verb;
+  print "\n";
+  print "-noapi     don't copy $0 to $ENV{HOME}/.bcast5\n";
+  print "\nCinelerraGG blend program compiler default configuration\n\n";
+  print "Compiler command line: $cin_compiler $cin_compopts $cin_ldopts -o <bp>.so <bp> $cin_ldlibs\n";
+  print "Optimizing options: $cin_optopts";
+  print " (active by default)" if $opt_opt;
+  print "\n";
+  print "Debugging options: $cin_debopts";
+  print " (active by default)" if $opt_deb;
+  print "\n";
+  print "Warning options: $cin_warnopts\n";
+  print "Editor command line: $cin_editor <bp>$cin_bgr\n";
+  print "\nRelevant environment variables\n\n";
+  if ($ENV{'CIN_CC'} ne '')
+  {
+    print "CIN_CC=$ENV{CIN_CC}\n";
+  }
+  else
+  {
+    print "\$CIN_CC: currently not set, fallback: $cin_compiler\n";
+  }
+  if ($ENV{'CC'} ne '')
+  {
+    print "CC=$ENV{CC}\n";
+  }
+  else
+  {
+    print "\$CC: currently not set, fallback: $cin_compiler\n";
+  }
+  if ($ENV{'CIN_EDITOR'} ne '')
+  {
+    print "CIN_EDITOR=$ENV{CIN_EDITOR}\n";
+  }
+  else
+  {
+    print "\$CIN_EDITOR: currently not set, fallback: $cin_editor\n";
+  }
+  if ($ENV{'CIN_DAT'} ne '')
+  {
+    print "CIN_DAT=$ENV{CIN_DAT}\n";
+  }
+  else
+  {
+    print "\$CIN_DAT: currently not set\n";
+  }
+  if ($ENV{'CIN_CONFIG'} ne '')
+  {
+    print "CIN_CONFIG=$ENV{CIN_CONFIG}\n";
+  }
+  else
+  {
+    print "\$CIN_CONFIG: currently not set, fallback: $ENV{HOME}/.bcast5\n";
+  }
+  if ($ENV{'CIN_USERLIB'} ne '')
+  {
+    print "CIN_USERLIB=$ENV{CIN_USERLIB}\n";
+  }
+  else
+  {
+    print "\$CIN_USERLIB: currently not set, fallback: $ENV{HOME}/.bcast5lib\n";
+  }
+  exit 0;
+}
+
+$opt_api = $opt_noapi = 0;
+$opt_edit = $opt_compile = $opt_cfile = $opt_warn = 0;
+$bp_src = '';
+
+# Scan options...
+foreach $arg (@ARGV)
+{
+  unless ($arg =~ /^-/)
+  {
+    $bp_src = $arg;                    # this is the source to compile
+    last;
+  }
+  if ($arg eq '-API')                  # print API version
+  {
+    $opt_api = 1;
+  }
+  elsif ($arg eq '-noapi')             # don't copy script to ~/.bcast5
+  {
+    $opt_noapi = 1;
+  }
+  elsif ($arg eq '-edit')              # open in text editor
+  {
+    $opt_edit = 1;
+    push @opts, $arg;
+  }
+  elsif ($arg eq '-compile')           # compile unconditionally
+  {
+    $opt_compile = 1;
+    push @opts, $arg;
+  }
+  elsif ($arg eq '-cfile')             # don't remove .c file
+  {
+    $opt_cfile = 1;
+    push @opts, $arg;
+  }
+  elsif ($arg eq '-opt')               # optimize
+  {
+    $opt_opt = 1;
+    push @opts, $arg;
+  }
+  elsif ($arg eq '-debug')             # debug
+  {
+    $opt_deb = 1;
+    push @opts, $arg;
+  }
+  elsif ($arg eq '-warn')              # warnings
+  {
+    $opt_warn = 1;
+    push @opts, $arg;
+  }
+  elsif ($arg eq '-verbose')           # print executed commands
+  {
+    $opt_verb = 1;
+    push @opts, $arg;
+  }
+  else { Usage(); }                    # unknown option
+}
+
+# A special internal request: output own API version
+if ($opt_api)
+{
+  print "$cin_bp_api\n";
+  exit 0;
+}
+
+# If a system (not user's) script instance is executed, and the API versions
+# of both scripts do not match, then copy the system script to the user's one
+# (making a backup copy of the latter). Then execute it with the same argument.
+if (! $opt_noapi && $0 ne $me_config)
+{
+  $me_api = 0;
+  $me_api = `\"$me_config\" -API` if -x $me_config;
+  #print "BlendProgramCompile: user script=$me_config API=$me_api, need $cin_bp_api\n";
+  if ($me_api != $cin_bp_api)
+  {
+    print "BlendProgramCompile: copying $0 to $me_config\n";
+    unlink "$me_config.bak" if -f "$me_config.bak";
+    rename "$me_config", "$me_config.bak" if -f $me_config;
+    system "cp \"$0\" \"$me_config\"";
+    system "chmod +x \"$me_config\"";
+  }
+}
+
+# Do nothing if nothing to compile
+if ($bp_src eq '') { Usage(); }
+#print "BlendProgramCompile: source=$bp_src\n";
+unless ($opt_edit || -f $bp_src) { Usage(); }
+
+# Redirection to user configurable script copy
+$arg = join ' ', @opts;
+if (! $opt_noapi && $0 ne $me_config)
+{
+  exec "\"$me_config\" $arg \"$bp_src\"" if -x $me_config;
+}
+
+# If a user's script instance is executed, do everything by myself
+print "BlendProgramCompile: executing $0 $arg $bp_src\n" if $opt_verb;
+
+# Call text editor
+if ($opt_edit)
+{
+  print "BlendProgramCompile: executing $cin_editor $bp_src$cin_bgr\n";
+  system "$cin_editor \"$bp_src\"$cin_bgr";
+  exit 0;
+}
+
+# Check if the program source is newer than the object
+if ($cin_dat ne '') { $bp_start = "$cin_dat/dlfcn/BlendProgramStart"; }
+else                { $bp_start = "BlendProgramStart"; }
+$mtime_start = -1;
+($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime_start,
+ $ctime, $blksize, $blocks) = stat ($bp_start);
+
+($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime_src,
+ $ctime, $blksize, $blocks) = stat ($bp_src);
+$mtime_src = $mtime_start if $mtime_src < $mtime_start;
+
+$mtime_so = -1;
+($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime_so,
+ $ctime, $blksize, $blocks) = stat ("$bp_src.so") if -f "$bp_src.so";
+
+if (! $opt_compile && $mtime_so > $mtime_src)
+{
+  print "BlendProgramCompile: $bp_src shared object up to date, exiting\n"
+    if $opt_verb;
+  exit 0;
+}
+
+# Call the compiler now
+$cin_compopts .= " $cin_optopts"  if $opt_opt;
+$cin_compopts .= " $cin_debopts"  if $opt_deb;
+$cin_compopts .= " $cin_warnopts" if $opt_warn;
+unlink "$bp_src.c" if -f "$bp_src.c";
+unlink "$bp_src.so" if -f "$bp_src.so";
+print "BlendProgramCompile: executing cat $bp_start $bp_src > $bp_src.c\n"
+  if $opt_verb;
+system "echo '# 1 \"$bp_start\"' > \"$bp_src.c\"";
+system "cat \"$bp_start\" >> \"$bp_src.c\"";
+system "echo '# 1 \"$bp_src\"' >> \"$bp_src.c\"";
+system "cat \"$bp_src\" >> \"$bp_src.c\"";
+print "BlendProgramCompile: executing $cin_compiler $cin_compopts $cin_ldopts -o $bp_src.so $bp_src.c $cin_ldlibs\n";
+system "$cin_compiler $cin_compopts $cin_ldopts -o \"$bp_src.so\" \"$bp_src.c\" $cin_ldlibs";
+unlink "$bp_src.c" if ! $opt_cfile && -f "$bp_src.c";
+
+# And finally return to the caller
+exit 0;
diff --git a/cinelerra-5.1/plugins/blendprogram/BlendProgramStart b/cinelerra-5.1/plugins/blendprogram/BlendProgramStart
new file mode 100644 (file)
index 0000000..1eae23c
--- /dev/null
@@ -0,0 +1,145 @@
+/***********************************************-*-C-*-**********/
+
+/* Blend program header for Cinelerra Blend Program plugin */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <math.h>
+#include <values.h>
+#include <limits.h>
+
+/* These six must match enum BC_CModel from guicast/bccmodels.h */
+#define BC_RGB888      9
+#define BC_RGBA8888   10
+#define BC_YUV888     13
+#define BC_YUVA8888   14
+#define BC_RGB_FLOAT  29
+#define BC_RGBA_FLOAT 30
+
+/* These five must match enum from plugins/blendprogram/blendprogram.h */
+#define AUTO    0
+#define RGB     1
+#define YUV     2
+#define HSV     3
+#define PROJECT 4
+
+/* Universal math macros taken mostly from guicast/clip.h */
+#ifndef ABS
+#define ABS(x) ((x) >= 0 ? (x) : -(x))
+#endif
+#ifndef SQR
+#define SQR(x) ((x) * (x))
+#endif
+#ifndef MAX
+#define MAX(x,y) ((x) > (y) ? (x) : (y))
+#endif
+#ifndef MIN
+#define MIN(x,y) ((x) < (y) ? (x) : (y))
+#endif
+#define TO_RAD(x) ((x) * 2 * M_PI / 360)
+#define TO_DEG(x) ((x) * 360 / 2 / M_PI)
+
+/* CLIP returns value, CLAMP does assignment */
+#define CLIP(x,y,z) ((x) < (y) ? (y) : ((x) > (z) ? (z) : (x)))
+#define CLAMP(x,y,z) ((x) = ((x) < (y) ? (y) : ((x) > (z) ? (z) : (x))))
+
+/* Macros usable in the BLEND_PROGRAM_INIT phase */
+
+/* Specification of working color space inside user's program */
+#define COLORSPACE_RGB {*color_work = RGB;}
+#define COLORSPACE_YUV {*color_work = YUV;}
+#define COLORSPACE_HSV {*color_work = HSV;}
+
+/* Minimum no of tracks required by user's program */
+#define REQUIRE_TRACKS(n) {*MIN_tracks = (n);}
+
+/* Claim parallelization support and inquire if parallelism requested */
+#define PARALLEL_SAFE {*parallel_support = 1;}
+#define PARALLEL_REQUEST parallel_request
+
+/* Dimensionality macros, can be used in both INIT and PROC phases */
+
+/* Total number of tracks got to work on */
+#define TOTAL_TRACKS N_tracks
+
+/* 1 == has alpha channel */
+#define HAS_ALPHA HAS_alpha
+
+/* Frame dimensions in pixels */
+#define WIDTH  FRAME_w
+#define HEIGHT FRAME_h
+
+/* Macros usable in the BLEND_PROGRAM_PROC phase */
+
+/* Handy macros for pixel and key color components */
+#define R(i) TRK_r[i]
+#define G(i) TRK_g[i]
+#define B(i) TRK_b[i]
+#define Y(i) TRK_r[i]
+#define U(i) TRK_g[i]
+#define V(i) TRK_b[i]
+#define H(i) TRK_r[i]
+#define S(i) TRK_g[i]
+#define A(i) TRK_a[i]
+
+#define KEY_R KEY_r
+#define KEY_G KEY_g
+#define KEY_B KEY_b
+#define KEY_Y KEY_r
+#define KEY_U KEY_g
+#define KEY_V KEY_b
+#define KEY_H KEY_r
+#define KEY_S KEY_g
+#define KEY_A KEY_a
+
+/* Macros for pixel coordinates */
+#define PIX_X TRK_x
+#define PIX_Y TRK_y
+
+/* Macros for various arts to clip pixels */
+#define CLIP_RGB(i) {CLAMP (TRK_r[i], 0, 1); \
+                    CLAMP (TRK_g[i], 0, 1); \
+                    CLAMP (TRK_b[i], 0, 1);}
+
+#define CLIP_YUV(i) {CLAMP (TRK_r[i],  0,   1);   \
+                    CLAMP (TRK_g[i], -0.5, 0.5); \
+                    CLAMP (TRK_b[i], -0.5, 0.5);}
+
+#define CLIP_HSV(i) {if (TRK_r[i] < 0 || TRK_r[i] >= 360) \
+                    TRK_r[i] -= floor(TRK_r[i]/360)*360; \
+                    CLAMP (TRK_g[i], 0, 1); CLAMP (TRK_b[i], 0, 1);}
+
+#define CLIP_A(i) {CLAMP (TRK_a[i], 0, 1);}
+
+#define CLIP_RGBA(i) { CLIP_RGB(i) CLIP_A(i) }
+#define CLIP_YUVA(i) { CLIP_YUV(i) CLIP_A(i) }
+#define CLIP_HSVA(i) { CLIP_HSV(i) CLIP_A(i) }
+
+#define CLIP_RGB_ALL {int I_track; \
+       for (I_track=0; I_track<N_tracks; I_track++) CLIP_RGBA(I_track)}
+
+#define CLIP_YUV_ALL {int I_track; \
+       for (I_track=0; I_track<N_tracks; I_track++) CLIP_YUVA(I_track)}
+
+#define CLIP_HSV_ALL {int I_track; \
+       for (I_track=0; I_track<N_tracks; I_track++) CLIP_HSVA(I_track)}
+
+/* Mandatory separators between different user's program blocks */
+/* Interfaces must match that from plugins/blendprogram/blendprogram.h */
+
+#define BLEND_PROGRAM_INIT                                                  \
+void bpInit (int *color_work, int color_proj, int *MIN_tracks, int N_tracks, \
+int *parallel_support, int parallel_request, int FRAME_w, int FRAME_h,       \
+int HAS_alpha) {
+
+#define BLEND_PROGRAM_PROC }                           \
+void bpProc (int N_tracks,                             \
+float *TRK_r, float *TRK_g, float *TRK_b, float *TRK_a, \
+float  KEY_r, float  KEY_g, float  KEY_b, float  KEY_a, \
+int    TRK_x, int    TRK_y, int  FRAME_w, int  FRAME_h, int HAS_alpha) {
+
+#define BLEND_PROGRAM_STOP {return;}
+
+#define BLEND_PROGRAM_END }
diff --git a/cinelerra-5.1/plugins/blendprogram/Makefile b/cinelerra-5.1/plugins/blendprogram/Makefile
new file mode 100644 (file)
index 0000000..539349d
--- /dev/null
@@ -0,0 +1,46 @@
+include ../../plugin_defs
+
+LDFLAGS += -ldl
+
+OBJS = $(OBJDIR)/blendprogram.o
+
+PLUGIN = blendprogram
+
+all::
+
+include ../../plugin_config
+
+CC ?= gcc
+
+FRONT = BlendProgramStart
+
+HELPER = BlendProgramCompile.pl
+
+DLFCN_DIR = $(BINDIR)/dlfcn
+DLFCN_BP_DIR = $(DLFCN_DIR)/bp
+
+BP_OBJS = \
+       chromakey.bp.so \
+       background.bp.so
+
+BP_SRCS = $(BP_OBJS:.bp.so=.bp)
+
+$(OBJDIR)/blendprogram.o: blendprogram.C blendprogram.h
+
+%.bp.so: %.bp $(FRONT) $(HELPER)
+       rm -f $@
+       CIN_CC=$(CC) CIN_DAT= ./$(HELPER) -noapi -compile -opt -warn $<
+
+all:: $(OUTPUT) $(BP_OBJS)
+
+install:: $(BP_OBJS)
+
+install::
+       mkdir -p $(DLFCN_DIR)
+       mkdir -p $(DLFCN_BP_DIR)
+       cp -a $(FRONT) $(HELPER) $(DLFCN_DIR)
+       chmod +x $(DLFCN_DIR)/$(HELPER)
+       cp -a $(BP_SRCS) $(BP_OBJS) $(DLFCN_BP_DIR)
+
+clean::
+       rm -f  $(BP_OBJS) *~
diff --git a/cinelerra-5.1/plugins/blendprogram/background.bp b/cinelerra-5.1/plugins/blendprogram/background.bp
new file mode 100644 (file)
index 0000000..926227f
--- /dev/null
@@ -0,0 +1,22 @@
+/***********************************************-*-C-*-**********/
+/* Fill transparencies with selected opaque background color */
+
+BLEND_PROGRAM_INIT
+
+COLORSPACE_RGB
+PARALLEL_SAFE
+
+BLEND_PROGRAM_PROC
+
+float fg, bg;
+
+fg = A(0);
+bg = 1-fg;
+
+R(0) = R(0) * fg + KEY_R * bg;
+G(0) = G(0) * fg + KEY_G * bg;
+B(0) = B(0) * fg + KEY_B * bg;
+
+A(0) = 1;
+
+BLEND_PROGRAM_END
diff --git a/cinelerra-5.1/plugins/blendprogram/blendprogram.C b/cinelerra-5.1/plugins/blendprogram/blendprogram.C
new file mode 100644 (file)
index 0000000..63cd99b
--- /dev/null
@@ -0,0 +1,2171 @@
+/*
+ * CINELERRA
+ * Copyright (C) 2008 Adam Williams <broadcast at earthling dot net>
+ *
+ * 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
+ *
+ */
+
+#include "filexml.h"
+#include "keyframe.h"
+#include "language.h"
+#include "mainerror.h"
+#include "clip.h"
+#include "bccolors.h"
+#include "loadbalance.h"
+#include "filesystem.h"
+#include "vframe.h"
+#include "mainsession.h"
+#include "mwindow.h"
+#include "pluginserver.h"
+#include "blendprogram.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <time.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <dlfcn.h>
+
+//#define DEBUG
+
+// Sorry for this global variable, it is needed to propagate the signal
+// from the GUI instance to the processing instance of the plugin
+// that some user function might have changed and need recompilation
+static time_t BlendProgramTstamp = -1;
+
+REGISTER_PLUGIN(BlendProgram)
+
+LOAD_CONFIGURATION_MACRO(BlendProgram, BlendProgramConfig)
+
+NEW_WINDOW_MACRO(BlendProgram, BlendProgramWindow)
+
+const char *BlendProgram::plugin_title() { return N_("Blend Program"); }
+
+int BlendProgram::is_realtime()     { return 1; }
+int BlendProgram::is_multichannel() { return 1; }
+int BlendProgram::is_synthesis()    { return 1; }
+
+////////////////////////////////////////////
+// Plugin configuration class implementation
+////////////////////////////////////////////
+
+BlendProgramConfig::BlendProgramConfig()
+{
+  funcname[0] = 0;                             // no function per default
+  parallel    = 1;                             // parallelize per default
+  clipcolors  = 1;                             // clip colors per default
+  direction   = BlendProgramConfig::BOTTOM_FIRST; // as in Overlay plugin
+  colorspace  = BlendProgramConfig::AUTO;      // requested from function
+  red = green = blue = 0;                      // black key color per default
+  alpha = 0;                                   // transparent per default
+}
+
+int BlendProgramConfig::equivalent(BlendProgramConfig &that)
+{
+  return
+    !strcmp (funcname, that.funcname) &&
+    parallel   == that.parallel       &&
+    clipcolors == that.clipcolors     &&
+    direction  == that.direction      &&
+    colorspace == that.colorspace     &&
+    EQUIV (red,   that.red)           &&
+    EQUIV (green, that.green)         &&
+    EQUIV (blue,  that.blue)          &&
+    EQUIV (alpha, that.alpha);
+}
+
+void BlendProgramConfig::copy_from(BlendProgramConfig &that)
+{
+  strcpy (funcname, that.funcname);
+  parallel   = that.parallel;
+  clipcolors = that.clipcolors;
+  direction  = that.direction;
+  colorspace = that.colorspace;
+  red        = that.red;
+  green      = that.green;
+  blue       = that.blue;
+  alpha      = that.alpha;
+}
+
+void BlendProgramConfig::interpolate (BlendProgramConfig &prev,
+                                     BlendProgramConfig &next,
+                                     int64_t prev_frame,
+                                     int64_t next_frame,
+                                     int64_t current_frame)
+{
+  double next_scale =
+    (double) (current_frame - prev_frame) / (next_frame - prev_frame);
+  double prev_scale =
+    (double) (next_frame - current_frame) / (next_frame - prev_frame);
+
+  red   = prev.red   * prev_scale + next.red   * next_scale;
+  green = prev.green * prev_scale + next.green * next_scale;
+  blue  = prev.blue  * prev_scale + next.blue  * next_scale;
+  alpha = prev.alpha * prev_scale + next.alpha * next_scale;
+
+  strcpy (funcname, prev.funcname);
+  parallel   = prev.parallel;
+  clipcolors = prev.clipcolors;
+  direction  = prev.direction;
+  colorspace = prev.colorspace;
+}
+
+const char *BlendProgramConfig::direction_to_text(int direction)
+{
+  switch(direction)
+  {
+  case BlendProgramConfig::BOTTOM_FIRST: return _("Bottom first");
+  case BlendProgramConfig::TOP_FIRST:    return _("Top first");
+  }
+  return "";
+}
+
+const char *BlendProgramConfig::colorspace_to_text(int colorspace)
+{
+  switch(colorspace)
+  {
+  case BlendProgramConfig::AUTO:    return _("auto");
+  case BlendProgramConfig::RGB:     return _("RGB");
+  case BlendProgramConfig::YUV:     return _("YUV");
+  case BlendProgramConfig::HSV:     return _("HSV");
+  case BlendProgramConfig::PROJECT: return _("of project");
+  }
+  return "";
+}
+
+int BlendProgramConfig::get_key_color()
+{
+  int red   = (int) (CLIP (this->red,   0, 1) * 255);
+  int green = (int) (CLIP (this->green, 0, 1) * 255);
+  int blue  = (int) (CLIP (this->blue,  0, 1) * 255);
+  return (red << 16) | (green << 8) | blue;
+}
+
+////////////////////////////////////////////
+// Plugin dialog window class implementation
+////////////////////////////////////////////
+
+BlendProgramFuncname::BlendProgramFuncname(BlendProgram *plugin,
+                                          const char *funcname,
+                                          BlendProgramWindow *gui,
+                                          int x, int y)
+  : BC_TextBox(x, y, gui->get_w()-x-xS(10), 1, funcname)
+{
+  this->plugin = plugin;
+  this->gui = gui;
+}
+
+int BlendProgramFuncname::handle_event()
+{
+  // Perhaps locking is not needed here
+  // as GUI is driven by a separate plugin instance
+  plugin->func_lock->lock("BlendProgramFuncname::handle_event");
+  strncpy(plugin->config.funcname, get_text(),
+         sizeof(plugin->config.funcname)-1);
+  BlendProgramTstamp = time(NULL);     // time of possible function change
+#ifdef DEBUG
+  printf ("BlendProgramFuncname::handle_event setting function %s\n   timestamp %s",
+         plugin->config.funcname, ctime(&BlendProgramTstamp));
+#endif
+  plugin->func_lock->unlock();
+  plugin->send_configure_change();
+  return 1;
+}
+
+BlendProgramDetach::BlendProgramDetach (BlendProgram *plugin,
+                                       BlendProgramWindow *gui,
+                                       int x, int y)
+  : BC_GenericButton (x, y, _("Detach"))
+{
+  this->plugin = plugin;
+  this->gui = gui;
+}
+
+int BlendProgramDetach::handle_event()
+{
+  if (! plugin->config.funcname[0]) return 1;// already detached, nothing to do
+
+  plugin->func_lock->lock("BlendProgramDetach::handle_event");
+  plugin->config.funcname[0] = 0;      // clear function, inducing detach
+  BlendProgramTstamp = time(NULL);     // force refresh of dlopen'd functions
+#ifdef DEBUG
+  printf ("BlendProgramDetach::handle_event clearing function\n   timestamp %s",
+         ctime(&BlendProgramTstamp));
+#endif
+  plugin->func_lock->unlock();
+
+  gui->lock_window("BlendProgramDetach::handle_event");
+  gui->funcname->update(plugin->config.funcname);
+  gui->unlock_window();
+
+  plugin->send_configure_change();
+  return 1;
+}
+
+BlendProgramRefresh::BlendProgramRefresh (BlendProgram *plugin,
+                                         BlendProgramWindow *gui,
+                                         int x, int y)
+  : BC_GenericButton (x, y, _("Refresh"))
+{
+  this->plugin = plugin;
+  this->gui = gui;
+}
+
+int BlendProgramRefresh::handle_event()
+{
+  plugin->func_lock->lock("BlendProgramRefresh::handle_event");
+  BlendProgramTstamp = time(NULL);     // force refresh of dlopen'd functions
+#ifdef DEBUG
+  printf ("BlendProgramRefresh::handle_event timestamp %s",
+         ctime(&BlendProgramTstamp));
+#endif
+  plugin->func_lock->unlock();
+  return 1;    // just reattach all functions, without reconfiguration
+}
+
+BlendProgramEdit::BlendProgramEdit (BlendProgram *plugin,
+                                   BlendProgramWindow *gui,
+                                   int x, int y)
+  : BC_GenericButton (x, y, _("Edit..."))
+{
+  this->plugin = plugin;
+  this->gui = gui;
+}
+
+int BlendProgramEdit::handle_event()
+{
+  char fname[BCTEXTLEN], dir[BCTEXTLEN], str[2*BCTEXTLEN];
+
+  strcpy (fname, plugin->config.funcname);
+  if (! fname[0])
+  {
+    eprintf (_("Blend Program: no source file to edit, select program first\n"));
+    return 1;
+  }
+
+  // Evtl make function path absolute by prepending project path to it
+  if (fname[0] != '/') // fname is relative, prepend current project path
+  {
+    strcpy (dir, plugin->server->mwindow->session->filename);
+    if (dir[0])
+    {
+      char *cp = strrchr (dir, '/');
+      if (cp)
+      {
+       cp[1] = 0;              // strip project filename off from project path
+       strcat (dir, fname);    // concatenate obtained path with function name
+       strcpy (fname, dir);
+      }
+    }
+  }
+
+  // This will run configured external editor via perl script
+  // If editor start is not backgrounded, GUI will block until editor exits
+  sprintf(str, "\"%s/dlfcn/BlendProgramCompile.pl\" -edit \"%s\"",
+         getenv("CIN_DAT"), fname);
+#ifdef DEBUG
+  printf ("BlendProgramEdit::handle_event: executing:\n   %s\n", str);
+#endif
+  system (str);                                // runs configured external editor
+
+  plugin->func_lock->lock("BlendProgramEdit::handle_event");
+  BlendProgramTstamp = time(NULL);     // force refresh of dlopen'd functions
+#ifdef DEBUG
+  printf ("BlendProgramEdit::handle_event edited function %s\n   timestamp %s",
+         fname, ctime(&BlendProgramTstamp));
+#endif
+  plugin->func_lock->unlock();
+
+  // Evtl functions will be recompiled, but no configure change
+  return 1;
+}
+
+BlendProgramFileButton::BlendProgramFileButton(BlendProgram *plugin,
+                                              BlendProgramWindow *gui,
+                                              int x, int y)
+  : BC_GenericButton(x, y, _("Attach..."))
+{
+  this->plugin = plugin;
+  this->gui = gui;
+  this->file_box = 0;
+}
+
+BlendProgramFileButton::~BlendProgramFileButton()
+{
+  stop();
+}
+
+int BlendProgramFileButton::handle_event()
+{
+  gui->editing_lock->lock();
+
+  if (! gui->editing)
+  {
+    gui->editing = 1;
+    gui->editing_lock->unlock();
+    start();
+  }
+  else
+  {
+    flicker();
+    gui->editing_lock->unlock();
+  }
+
+  return 1;
+}
+
+void BlendProgramFileButton::run()
+{
+  int result = 1;
+  const char *fpath;
+  char fname[BCTEXTLEN], dir[BCTEXTLEN];
+
+  strcpy (fname, plugin->config.funcname);
+
+  // This infinite loop is exited after clicking OK or Cancel in FileBox.
+  // There are several special buttons which replace the FileBox initial path
+  // with another predefined path and close FileBox with reinit_path flag set.
+  // If reinit_path is set, the loop is repeated with that extracted path.
+  // reinit_path is cleared inside BlendProgramFileBox constructor.
+
+  for (;;)             // will exit when reinit_path == 0
+  {    // Evtl make function path absolute by prepending project path to it
+    if (fname[0] != '/') // fname is relative, prepend current project path
+    {
+      strcpy (dir, plugin->server->mwindow->session->filename);
+      if (dir[0])
+      {
+       char *cp = strrchr (dir, '/');
+       if (cp)
+       {
+         cp[1] = 0;            // strip project filename off from project path
+         strcat (dir, fname);  // concatenate obtained path with function name
+         strcpy (fname, dir);
+       }
+      }
+    }
+#ifdef DEBUG
+    printf ("BlendProgramFileButton::run creating file_box (%s)\n", fname);
+#endif
+    file_box = new BlendProgramFileBox (plugin, gui, fname);
+    file_box->update_history();        // otherwise actual dir can be forgotten
+    file_box->create_objects();
+    file_box->lock_window ("BlendProgramFileButton::run");
+    file_box->add_objects();                   // add our special buttons
+    file_box->update_filter ("*.bp");
+    file_box->unlock_window();
+    result = file_box->run_window();
+    if (file_box->reinit_path)                 // if set, a button was clicked
+    {
+      fpath = file_box->get_current_path();    // current, as set by buttons
+#ifdef DEBUG
+      printf ("BlendProgramFileButton::run file_box returned %d reinit_path=%d\n   fpath=%s\n",
+             result, file_box->reinit_path, fpath);
+#endif
+      strncpy (fname, fpath ? fpath : "", sizeof(fname)-1);
+      delete file_box;
+      file_box = 0;
+      continue;        // reinit_path will be cleared on repeat in FileBox constructor
+    }
+    fpath = file_box->get_submitted_path();    // submitted, as set by user
+#ifdef DEBUG
+    printf ("BlendProgramFileButton::run file_box returned %d reinit_path=%d\n   fpath=%s\n",
+           result, file_box->reinit_path, fpath);
+#endif
+    strncpy (fname, fpath ? fpath : "", sizeof(fname)-1);
+    delete file_box;
+    file_box = 0;
+    break;             // reinit_path remains cleared, exit loop
+  }                    // until reinit_path == 0
+
+  gui->editing_lock->lock();
+  if (result) gui->editing = 0;
+  gui->editing_lock->unlock();
+  if (! gui->editing) return;                          // Cancel pressed
+
+  if (fname[0])                // selected function name not empty, canonicalize it
+  {    // if function is under project's dir, strip dir and make path relative
+    strcpy (dir, plugin->server->mwindow->session->filename);
+    // another project location might be plugin->server->mwindow->edl->path
+    if (dir[0])                // project filename contains some path
+    {
+      char *cp = strrchr (dir, '/');
+      if (cp)
+      {
+       cp[1] = 0;      // the directory of current project with trailing slash
+       if (! strncmp (fname, dir, strlen(dir)))
+       {
+         strcpy (dir, fname+strlen(dir));      // strip project dir off
+         strcpy (fname, dir+strspn(dir,"/"));  // ensure path is relative
+       }
+      }
+    }
+    if (strlen (fname) < 3 || strcmp (fname+strlen(fname)-3, ".bp"))
+      strcat (fname, ".bp");   // suggest '.bp' suffix for blend programs
+  }
+
+  // Actualize selected function in config and in the main plugin dialog
+  plugin->func_lock->lock("BlendProgramFileButton::run");
+  strcpy (plugin->config.funcname, fname);
+  BlendProgramTstamp = time(NULL);     // time of possible function change
+#ifdef DEBUG
+  printf ("BlendProgramFileButton::run setting function %s\n   timestamp %s",
+         plugin->config.funcname, ctime(&BlendProgramTstamp));
+#endif
+  plugin->func_lock->unlock();
+  gui->lock_window("BlendProgramFileButton::run");
+  gui->funcname->update(plugin->config.funcname);
+  gui->unlock_window();
+  gui->editing_lock->lock();
+  gui->editing = 0;
+  gui->editing_lock->unlock();
+
+  plugin->send_configure_change();
+}
+
+void BlendProgramFileButton::stop()
+{
+  if (file_box) file_box->set_done(1);
+  join();
+}
+
+BlendProgramFileBox::BlendProgramFileBox(BlendProgram *plugin,
+                                        BlendProgramWindow *gui,
+                                        char *init_path)
+  : BC_FileBox(0, BC_WindowBase::get_resources()->filebox_h/2, init_path,
+              _("Blend Program: Select program source file"),"")
+{
+  this->plugin = plugin;
+  this->gui = gui;
+
+  to_curdir   = 0;
+  to_usrlib   = 0;
+  to_syslib   = 0;
+  copy_curdir = 0;
+  copy_usrlib = 0;
+  file_edit   = 0;
+  reinit_path = 0;
+}
+
+BlendProgramFileBox::~BlendProgramFileBox()
+{
+}
+
+// We need several additional buttons not foreseen in the bare FileBox.
+// We arrange them in the place of (empty) FileBox caption.
+void BlendProgramFileBox::add_objects()
+{
+  int xs10 = xS(10), xs5 = xS(5);
+  int ys10 = yS(10);
+  int x = xs10, y = ys10, x2;
+
+  add_subwindow(to_curdir = new BlendProgramToCurdir(this, x, y));
+  x2 = x+to_curdir->get_w()+xs5;
+  add_subwindow(to_usrlib = new BlendProgramToUsrlib(this, x2, y));
+  x2 += to_usrlib->get_w()+xs5;
+  add_subwindow(to_syslib = new BlendProgramToSyslib(this, x2, y));
+  y = get_y_margin();
+  add_subwindow(copy_curdir = new BlendProgramCopyCurdir(this, x, y));
+  x2 = x+copy_curdir->get_w()+xs5;
+  add_subwindow(copy_usrlib = new BlendProgramCopyUsrlib(this, x2, y));
+  x2 += copy_usrlib->get_w()+xs5;
+  add_subwindow(file_edit = new BlendProgramFileEdit(this, x2, y));
+  flush();
+}
+
+int BlendProgramFileBox::resize_event(int w, int h)
+{
+  int xs10 = xS(10), xs5 = xS(5);
+  int x = xs10, y, x2;
+
+  BC_FileBox::resize_event (w, h);
+
+  y = get_y_margin();
+  copy_curdir->reposition_window (x, y);
+  x2 = x+copy_curdir->get_w()+xs5;
+  copy_usrlib->reposition_window (x2, y);
+  x2 += copy_usrlib->get_w()+xs5;
+  file_edit->reposition_window (x2, y);
+
+  flush();
+  return 1;
+}
+
+BlendProgramToCurdir::BlendProgramToCurdir(BlendProgramFileBox *file_box,
+                                          int x, int y)
+  : BC_GenericButton (x, y, _("=>Project"))
+{
+  this->file_box = file_box;
+}
+
+int BlendProgramToCurdir::handle_event()
+{
+  char *cp, fname[BCTEXTLEN], dir[BCTEXTLEN], path[BCTEXTLEN];
+
+  strcpy (dir, file_box->plugin->server->mwindow->session->filename);
+  if (dir[0])                  // first get current project directory
+  {
+    cp = strrchr (dir, '/');
+    if (cp) *cp = 0;
+    else dir[0] = 0;
+  }
+  if (! dir[0])                        // no project dir - get curdir as fallback
+  {
+    cp = getcwd (dir, sizeof(dir));
+    if (! cp) dir[0] = 0;
+  }
+  if (! dir[0]) return 1;      // no curdir accessible, nothing to change
+
+  fname[0] = 0;
+  const char *spath = file_box->get_submitted_path();// get name entered so far
+  if (spath) file_box->fs->extract_name (fname, spath);        // cut name from dir
+
+  if (fname[0]) file_box->fs->join_names (path, dir, fname);
+  else strcpy (path, dir);     // substitute old entered dir with project dir
+
+  // Not exactly sure what operations on FileBox are really important
+  file_box->fs->change_dir (dir);      // force it to recognize the new dir
+
+  // This updates all paths, sets current_path and submitted_path of FileBox,
+  // but in memory only, text fields in the dialog are not actualized.
+  // file_box->refresh() does not help to refresh text fields either.
+  // Therefore we have to apply a trick with closing FileBox and
+  // reopening it with the new generated path.
+  file_box->update_paths (path);
+
+  // Without updating history FileBox forgets our new dir
+  // and sets curdir to some old history item.
+  file_box->update_history();
+
+  file_box->reinit_path = 1;   // set flag to reopen FileBox afterwards
+  file_box->set_done(1); // temporarily close FileBox, will be reopened later
+
+  return 1;
+}
+
+BlendProgramToUsrlib::BlendProgramToUsrlib(BlendProgramFileBox *file_box,
+                                          int x, int y)
+  : BC_GenericButton (x, y, _("=>Userlib"))
+{
+  this->file_box = file_box;
+}
+
+int BlendProgramToUsrlib::handle_event()
+{
+  char *cp, fname[BCTEXTLEN], dir[BCTEXTLEN], path[BCTEXTLEN];
+
+  dir[0] = 0;
+  cp = getenv ("CIN_USERLIB");         // $HOME/.bcast5lib by default
+  if (cp) strcpy (dir, cp);
+  if (! dir[0])
+  {
+    cp = getenv ("HOME");              // evtl resolve as default via $HOME
+    if (cp) strcpy (dir, cp);
+    if (dir[0]) strcat (dir, "/.bcast5lib");
+    else
+    {
+      cp = getenv ("CIN_CONFIG");      // or via $CIN_CONFIG as fallback
+      if (cp) strcpy (dir, cp);
+      if (dir[0]) strcat (dir, "lib");
+    }
+  }
+  if (! dir[0]) return 1;      // no user libdir known, nothing to change
+
+  // The default user libdir for blend programs is $HOME/.bcast5lib/dlfcn/bp
+  // Ensure it is a directory, evtl create dir, if not - do nothing else
+  if (! file_box->fs->is_dir (dir)) file_box->fs->create_dir (dir);
+  if (! file_box->fs->is_dir (dir)) return 1;
+  strcat (dir, "/dlfcn");
+  if (! file_box->fs->is_dir (dir)) file_box->fs->create_dir (dir);
+  if (! file_box->fs->is_dir (dir)) return 1;
+  strcat (dir, "/bp");
+  if (! file_box->fs->is_dir (dir)) file_box->fs->create_dir (dir);
+  if (! file_box->fs->is_dir (dir)) return 1;
+
+  fname[0] = 0;
+  const char *spath = file_box->get_submitted_path();// get name entered so far
+  if (spath) file_box->fs->extract_name (fname, spath);        // cut name from dir
+  if (fname[0]) file_box->fs->join_names (path, dir, fname);
+  else strcpy (path, dir);     // substitute old entered dir with user libdir
+
+  // Reinitialize FileBox with the modified path
+  file_box->fs->change_dir (dir);
+  file_box->update_paths (path);
+  file_box->update_history();
+  file_box->reinit_path = 1;   // set flag to reopen FileBox afterwards
+  file_box->set_done(1); // temporarily close FileBox, will be reopened later
+
+  return 1;
+}
+
+BlendProgramToSyslib::BlendProgramToSyslib(BlendProgramFileBox *file_box,
+                                          int x, int y)
+  : BC_GenericButton (x, y, _("=>Syslib"))
+{
+  this->file_box = file_box;
+}
+
+int BlendProgramToSyslib::handle_event()
+{
+  char *cp, fname[BCTEXTLEN], dir[BCTEXTLEN], path[BCTEXTLEN];
+
+  dir[0] = 0;
+  cp = getenv ("CIN_DAT");     // Cinelerra installation directory (bin)
+  if (cp) strcpy (dir, cp);
+  if (! dir[0]) return 1;      // there is no default
+
+  // System libdir for blend programs is $CIN_DAT/dlfcn/bp (bin/dlfcn/bp).
+  // Ensure it is a directory, it must exist, if not - do nothing else
+  strcat (dir, "/dlfcn/bp");
+  if (! file_box->fs->is_dir (dir)) return 1;
+
+  fname[0] = 0;
+  const char *spath = file_box->get_submitted_path();// get name entered so far
+  if (spath) file_box->fs->extract_name (fname, spath);        // cut name from dir
+  if (fname[0]) file_box->fs->join_names (path, dir, fname);
+  else strcpy (path, dir);     // substitute that old dir with system libdir
+
+  // Reinitialize FileBox with the modified path
+  file_box->fs->change_dir (dir);
+  file_box->update_paths (path);
+  file_box->update_history();
+  file_box->reinit_path = 1;   // set flag to reopen FileBox afterwards
+  file_box->set_done(1); // temporarily close FileBox, will be reopened later
+
+  return 1;
+}
+
+BlendProgramCopyCurdir::BlendProgramCopyCurdir(BlendProgramFileBox *file_box,
+                                              int x, int y)
+  : BC_GenericButton (x, y, _("Copy to project"))
+{
+  this->file_box = file_box;
+}
+
+int BlendProgramCopyCurdir::handle_event()
+{
+  int ret;
+  char *cp, fname[BCTEXTLEN], dir[BCTEXTLEN], from_path[BCTEXTLEN],
+    to_path[BCTEXTLEN], cmd[3*BCTEXTLEN];
+
+  strcpy (dir, file_box->plugin->server->mwindow->session->filename);
+  if (dir[0])                  // first get current project directory
+  {
+    cp = strrchr (dir, '/');
+    if (cp) *cp = 0;
+    else dir[0] = 0;
+  }
+  if (! dir[0]) return 1;      // no curdir accessible, no copy target
+
+  fname[0] = from_path[0] = 0;
+  const char *spath = file_box->get_submitted_path();// get name entered so far
+  if (spath)
+  {
+    strcpy (from_path, spath);                 // this is copy source
+    file_box->fs->extract_name (fname, spath); // cut name from source dir
+  }
+  if (! (fname[0] && from_path[0])) return 1;  // no copy source ??
+
+  file_box->fs->join_names (to_path, dir, fname);      // this is copy target
+
+  if (! strcmp (from_path, to_path)) return 1; // source and target identical
+
+  if (file_box->fs->is_dir (from_path) || file_box->fs->is_dir (to_path))
+    return 1;                  // source and target must not be directories
+  if (access (from_path, R_OK))
+  {
+    eprintf (_("Blend Program: source file %s does not exist or not readable\n"),
+            from_path);
+    return 1;
+  }
+  if (! access (to_path, F_OK))
+  {
+    eprintf (_("Blend Program: target file %s exists, overwriting not allowed\n"),
+            to_path);
+    return 1;
+  }
+
+  // Now do copy operation
+  sprintf (cmd, "cp \"%s\" \"%s\"", from_path, to_path);
+#ifdef DEBUG
+  printf ("BlendProgramCopyCurdir::handle_event: executing %s\n", cmd);
+#endif
+  ret = system (cmd);
+  if (ret)
+  {
+    eprintf (_("Blend Program: copying %s to %s failed\nsee console printout for diagnostics\n"),
+            from_path, to_path);
+    return 1;
+  }
+
+  // Copying successful, now change dir to the location of the target
+  file_box->fs->change_dir (dir);
+  file_box->update_paths (to_path);
+  file_box->update_history();
+  file_box->reinit_path = 1;   // set flag to reopen FileBox afterwards
+  file_box->set_done(1); // temporarily close FileBox, will be reopened later
+
+  return 1;
+}
+
+BlendProgramCopyUsrlib::BlendProgramCopyUsrlib(BlendProgramFileBox *file_box,
+                                              int x, int y)
+  : BC_GenericButton (x, y, _("Copy to userlib"))
+{
+  this->file_box = file_box;
+}
+
+int BlendProgramCopyUsrlib::handle_event()
+{
+  int ret;
+  char *cp, fname[BCTEXTLEN], dir[BCTEXTLEN], from_path[BCTEXTLEN],
+    to_path[BCTEXTLEN], cmd[3*BCTEXTLEN];
+
+  dir[0] = 0;
+  cp = getenv ("CIN_USERLIB");         // $HOME/.bcast5lib by default
+  if (cp) strcpy (dir, cp);
+  if (! dir[0])
+  {
+    cp = getenv ("HOME");              // evtl resolve as default via $HOME
+    if (cp) strcpy (dir, cp);
+    if (dir[0]) strcat (dir, "/.bcast5lib");
+    else
+    {
+      cp = getenv ("CIN_CONFIG");      // or via $CIN_CONFIG as fallback
+      if (cp) strcpy (dir, cp);
+      if (dir[0]) strcat (dir, "lib");
+    }
+  }
+  if (! dir[0]) return 1;      // no user libdir known, no copy target
+
+  // Evtl create user libdir, if not successful - do nothing else
+  if (! file_box->fs->is_dir (dir)) file_box->fs->create_dir (dir);
+  if (! file_box->fs->is_dir (dir)) return 1;
+  strcat (dir, "/dlfcn");
+  if (! file_box->fs->is_dir (dir)) file_box->fs->create_dir (dir);
+  if (! file_box->fs->is_dir (dir)) return 1;
+  strcat (dir, "/bp");
+  if (! file_box->fs->is_dir (dir)) file_box->fs->create_dir (dir);
+  if (! file_box->fs->is_dir (dir)) return 1;
+
+  fname[0] = from_path[0] = 0;
+  const char *spath = file_box->get_submitted_path();// get name entered so far
+  if (spath)
+  {
+    strcpy (from_path, spath);                 // this is copy source
+    file_box->fs->extract_name (fname, spath); // cut name from source dir
+  }
+  if (! (fname[0] && from_path[0])) return 1;  // no copy source ??
+
+  file_box->fs->join_names (to_path, dir, fname);      // this is copy target
+
+  if (! strcmp (from_path, to_path)) return 1; // source and target identical
+
+  if (file_box->fs->is_dir (from_path) || file_box->fs->is_dir (to_path))
+    return 1;                  // source and target must not be directories
+  if (access (from_path, R_OK))
+  {
+    eprintf (_("Blend Program: source file %s does not exist or not readable\n"),
+            from_path);
+    return 1;
+  }
+  if (! access (to_path, F_OK))
+  {
+    eprintf (_("Blend Program: target file %s exists, overwriting not allowed\n"),
+            to_path);
+    return 1;
+  }
+
+  // Now do copy operation
+  sprintf (cmd, "cp \"%s\" \"%s\"", from_path, to_path);
+#ifdef DEBUG
+  printf ("BlendProgramCopyUsrlib::handle_event: executing %s\n", cmd);
+#endif
+  ret = system (cmd);
+  if (ret)
+  {
+    eprintf (_("Blend Program: copying %s to %s failed\nsee console printout for diagnostics\n"),
+            from_path, to_path);
+    return 1;
+  }
+
+  return 1; // Copying successful, but don't change directory to user libdir
+}
+
+BlendProgramFileEdit::BlendProgramFileEdit(BlendProgramFileBox *file_box,
+                                          int x, int y)
+  : BC_GenericButton (x, y, _("Edit..."))
+{
+  this->file_box = file_box;
+}
+
+int BlendProgramFileEdit::handle_event()
+{
+  char fname[BCTEXTLEN], dir[BCTEXTLEN], str[2*BCTEXTLEN];
+
+  fname[0] = 0;
+  const char *spath = file_box->get_submitted_path();// get name entered so far
+  if (spath) strcpy (fname, spath);
+  if (! fname[0])
+  {
+    eprintf (_("Blend Program: no program to edit, select source file first\n"));
+    return 1;
+  }
+
+  // Evtl make function path absolute by prepending project path to it
+  if (fname[0] != '/') // fname is relative, prepend current project path
+  {
+    strcpy (dir, file_box->plugin->server->mwindow->session->filename);
+    if (dir[0])
+    {
+      char *cp = strrchr (dir, '/');
+      if (cp)
+      {
+       cp[1] = 0;              // strip project filename off from project path
+       strcat (dir, fname);    // concatenate obtained path with function name
+       strcpy (fname, dir);
+      }
+    }
+  }
+  if (file_box->fs->is_dir (fname))
+  {
+    eprintf (_("Blend Program: cannot edit directory, select source file first\n"));
+    return 1;
+  }
+
+  // This will run configured external editor via perl script
+  // If editor start is not backgrounded, GUI will block until editor exits
+  sprintf(str, "\"%s/dlfcn/BlendProgramCompile.pl\" -edit \"%s\"",
+         getenv("CIN_DAT"), fname);
+#ifdef DEBUG
+  printf ("BlendProgramFileEdit::handle_event: executing:\n   %s\n", str);
+#endif
+  system (str);                                // runs configured external editor
+
+  file_box->plugin->func_lock->lock("BlendProgramFileEdit::handle_event");
+  BlendProgramTstamp = time(NULL);     // force refresh of dlopen'd functions
+#ifdef DEBUG
+  printf ("BlendProgramFileEdit::handle_event edited function %s\n   timestamp %s",
+         fname, ctime(&BlendProgramTstamp));
+#endif
+  file_box->plugin->func_lock->unlock();
+
+  return 1;
+}
+
+BlendProgramClipcolors::BlendProgramClipcolors(BlendProgram *plugin,
+                                              BlendProgramWindow *gui,
+                                              int x, int y)
+  : BC_CheckBox(x, y, plugin->config.clipcolors)
+{
+  this->plugin = plugin;
+  this->gui = gui;
+}
+
+int BlendProgramClipcolors::handle_event()
+{
+  plugin->config.clipcolors = get_value();
+  plugin->send_configure_change();
+  return 1;
+}
+
+BlendProgramParallel::BlendProgramParallel(BlendProgram *plugin,
+                                          BlendProgramWindow *gui,
+                                          int x, int y)
+  : BC_CheckBox(x, y, plugin->config.parallel)
+{
+  this->plugin = plugin;
+  this->gui = gui;
+}
+
+int BlendProgramParallel::handle_event()
+{
+  plugin->config.parallel = get_value();
+  plugin->send_configure_change();
+  return 1;
+}
+
+BlendProgramDirection::BlendProgramDirection(BlendProgram *plugin, int x, int y)
+  : BC_PopupMenu(x, y, xS(150),
+       BlendProgramConfig::direction_to_text(plugin->config.direction), 1)
+{
+  this->plugin = plugin;
+}
+
+void BlendProgramDirection::create_objects()
+{
+  add_item(new BC_MenuItem(BlendProgramConfig::direction_to_text(
+                            BlendProgramConfig::TOP_FIRST)));
+  add_item(new BC_MenuItem(BlendProgramConfig::direction_to_text(
+                            BlendProgramConfig::BOTTOM_FIRST)));
+}
+
+int BlendProgramDirection::handle_event()
+{
+  char *text = get_text();
+
+  if(!strcmp(text, BlendProgramConfig::direction_to_text(
+              BlendProgramConfig::TOP_FIRST)))
+    plugin->config.direction = BlendProgramConfig::TOP_FIRST;
+  else if(!strcmp(text, BlendProgramConfig::direction_to_text(
+                   BlendProgramConfig::BOTTOM_FIRST)))
+    plugin->config.direction = BlendProgramConfig::BOTTOM_FIRST;
+
+  plugin->send_configure_change();
+  return 1;
+}
+
+BlendProgramColorspace::BlendProgramColorspace(BlendProgram *plugin,
+                                              int x, int y)
+  : BC_PopupMenu(x, y, xS(150),
+       BlendProgramConfig::colorspace_to_text(plugin->config.colorspace), 1)
+{
+  this->plugin = plugin;
+}
+
+void BlendProgramColorspace::create_objects()
+{
+  add_item(new BC_MenuItem(BlendProgramConfig::colorspace_to_text(
+                            BlendProgramConfig::AUTO)));
+  add_item(new BC_MenuItem(BlendProgramConfig::colorspace_to_text(
+                            BlendProgramConfig::RGB)));
+  add_item(new BC_MenuItem(BlendProgramConfig::colorspace_to_text(
+                            BlendProgramConfig::YUV)));
+  add_item(new BC_MenuItem(BlendProgramConfig::colorspace_to_text(
+                            BlendProgramConfig::HSV)));
+  add_item(new BC_MenuItem(BlendProgramConfig::colorspace_to_text(
+                            BlendProgramConfig::PROJECT)));
+}
+
+int BlendProgramColorspace::handle_event()
+{
+  char *text = get_text();
+
+  if(!strcmp(text, BlendProgramConfig::colorspace_to_text(
+              BlendProgramConfig::AUTO)))
+    plugin->config.colorspace = BlendProgramConfig::AUTO;
+  else if(!strcmp(text, BlendProgramConfig::colorspace_to_text(
+                   BlendProgramConfig::RGB)))
+    plugin->config.colorspace = BlendProgramConfig::RGB;
+  else if(!strcmp(text, BlendProgramConfig::colorspace_to_text(
+                   BlendProgramConfig::YUV)))
+    plugin->config.colorspace = BlendProgramConfig::YUV;
+  else if(!strcmp(text, BlendProgramConfig::colorspace_to_text(
+                   BlendProgramConfig::HSV)))
+    plugin->config.colorspace = BlendProgramConfig::HSV;
+  else if(!strcmp(text, BlendProgramConfig::colorspace_to_text(
+                   BlendProgramConfig::PROJECT)))
+    plugin->config.colorspace = BlendProgramConfig::PROJECT;
+
+  plugin->send_configure_change();
+  return 1;
+}
+
+BlendProgramKeyColor::BlendProgramKeyColor (BlendProgram *plugin,
+                                           BlendProgramWindow *gui,
+                                           int x, int y)
+  : BC_GenericButton (x, y, _("Select key color..."))
+{
+  this->plugin = plugin;
+  this->gui = gui;
+}
+
+int BlendProgramKeyColor::handle_event()
+{
+  gui->color_thread->start_window (plugin->config.get_key_color(), 0xff);
+  return 1;
+}
+
+BlendProgramColorPicker::BlendProgramColorPicker (BlendProgram *plugin,
+                                                 BlendProgramWindow *gui,
+                                                 int x, int y)
+  : BC_GenericButton (x, y, _("Get from color picker"))
+{
+  this->plugin = plugin;
+  this->gui = gui;
+}
+
+int BlendProgramColorPicker::handle_event()
+{
+  plugin->config.red   = plugin->get_red();
+  plugin->config.green = plugin->get_green();
+  plugin->config.blue  = plugin->get_blue();
+
+  gui->update_key_sample();
+
+  plugin->send_configure_change();
+  return 1;
+}
+
+BlendProgramColorThread::BlendProgramColorThread (BlendProgram * plugin,
+                                                 BlendProgramWindow * gui)
+  : ColorPicker (0, _("Select color"))
+{
+  this->plugin = plugin;
+  this->gui = gui;
+}
+
+int BlendProgramColorThread::handle_new_color (int output, int alpha)
+{
+  plugin->config.red   = (float) ((output & 0xff0000) >> 16) / 255;
+  plugin->config.green = (float) ((output & 0x00ff00) >>  8) / 255;
+  plugin->config.blue  = (float) ((output & 0x0000ff)      ) / 255;
+
+  get_gui()->unlock_window();
+  gui->lock_window("BlendProgramColorThread::handle_new_color");
+  gui->update_key_sample();
+  gui->unlock_window();
+  get_gui()->lock_window("BlendProgramColorThread::handle_new_color");
+
+  plugin->send_configure_change();
+  return 1;
+}
+
+BlendProgramAlphaText::BlendProgramAlphaText(BlendProgram *plugin,
+                                            BlendProgramWindow *gui,
+                                            BlendProgramAlphaSlider *slider,
+                                            int x, int y,
+                                            float min, float max,
+                                            float *output)
+  : BC_TumbleTextBox(gui, *output, min, max, x, y, xS(60), 2)
+{
+  this->plugin = plugin;
+  this->gui = gui;
+  this->slider = slider;
+  this->min = min;
+  this->max = max;
+  this->output = output;
+  set_increment(0.01);
+}
+
+BlendProgramAlphaText::~BlendProgramAlphaText()
+{
+}
+
+int BlendProgramAlphaText::handle_event()
+{
+  *output = atof(get_text());
+  if(*output > max) *output = max;
+  if(*output < min) *output = min;
+  slider->update(*output);
+
+  plugin->send_configure_change();
+  return 1;
+}
+
+BlendProgramAlphaSlider::BlendProgramAlphaSlider(BlendProgram *plugin,
+                                                BlendProgramAlphaText *text,
+                                                int x, int y, int w,
+                                                float min, float max,
+                                                float *output)
+  : BC_FSlider(x, y, 0, w, w, min, max, *output)
+{
+  this->plugin = plugin;
+  this->text = text;
+  this->output = output;
+  set_precision(0.01);
+  enable_show_value(0);                // Hide caption
+}
+
+BlendProgramAlphaSlider::~BlendProgramAlphaSlider()
+{
+}
+
+int BlendProgramAlphaSlider::handle_event()
+{
+  *output = get_value();
+  text->update(*output);
+
+  plugin->send_configure_change();
+  return 1;
+}
+
+BlendProgramWindow::BlendProgramWindow(BlendProgram *plugin)
+  : PluginClientWindow(plugin, xS(450), yS(380), xS(450), yS(380), 0)
+{
+  this->plugin = plugin;
+  color_thread = 0;
+  editing_lock = new Mutex("BlendProgramWindow::editing_lock");
+  editing = 0;
+}
+
+BlendProgramWindow::~BlendProgramWindow()
+{
+  delete color_thread;
+  delete editing_lock;
+}
+
+void BlendProgramWindow::create_objects()
+{
+  int xs5 = xS(5), xs10 = xS(10), xs20 = xS(20);
+  int ys5 = yS(5), ys10 = yS(10), ys20 = yS(20), ys30 = yS(30), ys40 = yS(40);
+  int x = xs10, y = ys10, x2;
+  BC_Title *title;
+  BC_TitleBar *title_bar;
+
+  // Programming section
+  add_subwindow (title_bar =
+                new BC_TitleBar(x, y, get_w()-2*x, xs20, xs10,
+                                _("Blend programming environment")));
+
+  y += ys30;
+  add_subwindow(title = new BC_Title(x, y, _("Program:")));
+  add_subwindow(funcname =
+               new BlendProgramFuncname(plugin, plugin->config.funcname, this,
+                                        x + title->get_w() + xs5, y));
+
+  y += ys30;
+  add_subwindow(file_button = new BlendProgramFileButton(plugin, this, x, y));
+  x2 = x+file_button->get_w()+xs5;
+  add_subwindow(edit_button = new BlendProgramEdit(plugin, this, x2, y));
+  x2 += edit_button->get_w()+xs5;
+  add_subwindow(refresh_button = new BlendProgramRefresh(plugin, this, x2, y));
+  x2 += refresh_button->get_w()+xs5;
+  add_subwindow(detach_button = new BlendProgramDetach(plugin, this, x2, y));
+
+  y += ys30;
+  add_subwindow(title = new BC_Title(x, y, _("Color space:")));
+  add_subwindow(colorspace =
+               new BlendProgramColorspace(plugin,
+                                          x + title->get_w() + xs5, y));
+  colorspace->create_objects();
+
+  x2 = x+title->get_w()+colorspace->get_w()+xs10+xs10;
+  add_subwindow(title = new BC_Title(x2, y, _("Parallelize processing")));
+  add_subwindow(parallel =
+               new BlendProgramParallel(plugin, this,
+                                        x2 + title->get_w() + xs5, y));
+
+  // Supplementary color section
+  y += ys40;
+  add_subwindow (title_bar =
+                new BC_TitleBar(x, y, get_w()-2*x, xs20, xs10,
+                                _("Supplementary color selection")));
+
+  y += ys30;
+  add_subwindow(title =
+               new BC_Title(x, y, _("Chroma key or substitution color:")));
+  add_subwindow(key_sample = new BC_SubWindow(x + title->get_w() + xs5, y,
+                                             xS(150), yS(50)));
+  y += ys20+ys5;
+  add_subwindow(title = new BC_Title(x, y, _("Clip color values")));
+  add_subwindow(clipcolors =
+               new BlendProgramClipcolors(plugin, this,
+                                          x + title->get_w() + xs5, y));
+
+  y += ys30+ys5;
+  add_subwindow(key_color = new BlendProgramKeyColor(plugin, this, x, y));
+  x2 = x+key_color->get_w()+xs5;
+  add_subwindow(color_picker =
+               new BlendProgramColorPicker(plugin, this, x2, y));
+
+  y += ys30+ys5;
+  add_subwindow(title = new BC_Title(x, y, _("Substitution opacity:")));
+  alpha_text = new BlendProgramAlphaText (plugin, this, 0,
+                                         x+title->get_w()+xs10+xs5+xS(210),
+                                         y, 0, 1, &plugin->config.alpha);
+  alpha_text->create_objects();
+  key_alpha = new BlendProgramAlphaSlider (plugin, alpha_text,
+                                          x + title->get_w() + xs5, y,
+                                          xS(210), 0, 1,
+                                          &plugin->config.alpha);
+  add_subwindow(key_alpha);
+  alpha_text->slider = key_alpha;
+
+  // Track arrangement section
+  y += ys40;
+  add_subwindow (title_bar =
+                new BC_TitleBar(x, y, get_w()-2*x, xs20, xs10,
+                                _("Processed tracks arrangement")));
+
+  y += ys30;
+  add_subwindow(title = new BC_Title(x, y, _("Track order:")));
+  add_subwindow(direction =
+               new BlendProgramDirection(plugin,
+                                         x + title->get_w() + xs5, y));
+  direction->create_objects();
+
+  color_thread = new BlendProgramColorThread(plugin, this);
+
+  update_key_sample();
+  show_window();
+  flush();
+}
+
+void BlendProgramWindow::update_key_sample()
+{
+  key_sample->set_color (plugin->config.get_key_color());
+  key_sample->draw_box (0, 0, key_sample->get_w(), key_sample->get_h());
+  key_sample->set_color (BLACK);
+  key_sample->draw_rectangle (0, 0, key_sample->get_w(), key_sample->get_h());
+  key_sample->flash ();
+}
+
+void BlendProgramWindow::done_event()
+{
+  color_thread->close_window();
+}
+
+int BlendProgramWindow::close_event()
+{
+  color_thread->close_window();
+  file_button->stop();
+  set_done(1);
+  return 1;
+}
+
+int BlendProgramWindow::hide_window (int flush)
+{
+  color_thread->close_window();
+  file_button->stop();
+  return BC_WindowBase::hide_window (flush);
+}
+
+////////////////////////////////////////////
+// Plugin main class implementation
+////////////////////////////////////////////
+
+BlendProgramFunc::BlendProgramFunc()
+{
+  src[0] =  0;
+  handle =  0;
+  proc   =  0;
+  init   =  0;
+  tstamp = -1;
+}
+
+BlendProgramFunc::~BlendProgramFunc()
+{
+  if (handle)
+  {
+#ifdef DEBUG
+    printf ("BlendProgramFunc destructor detaching function dlclose(%s)\n",
+           src);
+#endif
+    dlclose (handle);
+  }
+}
+
+BlendProgram::BlendProgram(PluginServer *server)
+  : PluginVClient(server)
+{
+  BlendProgramTstamp = time(NULL);
+  inspect_configuration = 1;                   // force initial configuration
+  curr_func_no = -1;
+  func_lock = new Mutex("BlendProgram::func_lock");
+  engine = 0;
+#ifdef DEBUG
+  printf ("BlendProgram constructor timestamp %s", ctime(&BlendProgramTstamp));
+#endif
+}
+
+BlendProgram::~BlendProgram()
+{
+  if (engine) delete engine;
+  delete func_lock;
+#ifdef DEBUG
+  printf ("BlendProgram destructor removing all %d functions\n",
+         funclist.total);
+#endif
+  funclist.remove_all_objects();
+  curr_func.handle = 0;
+}
+
+int BlendProgram::process_buffer(VFrame **frame,
+                                int64_t start_position,
+                                double frame_rate)
+{
+  BlendProgramFunc *ptr;
+  int refresh_eprintf = 0;
+
+  // Mocking up with function shared object if it might get modified
+  // Not sure if plugin locking is needed for this separate processing instance
+  func_lock->lock("BlendProgram::process_buffer");
+
+  // First check if function name was changed
+  if (load_configuration() || inspect_configuration) // function might change
+  {
+    inspect_configuration = 0; // do once after change or after creation
+    if (strcmp (curr_func.src, config.funcname))       // function changed
+    {
+      curr_func.src[0] = 0;
+      curr_func.handle = 0;
+      curr_func.proc   = 0;
+      curr_func.init   = 0;
+      curr_func.tstamp = -1;
+      curr_func_no = -1;
+      if (config.funcname[0])                  // function name not empty
+      {
+       refresh_eprintf = 1;                    // probably new function
+       strcpy (curr_func.src, config.funcname);
+#ifdef DEBUG
+       printf ("BlendProgram::process_buffer searching function %s out of %d\n",
+               curr_func.src, funclist.total);
+#endif
+       for (int i=0; i<funclist.total; i++)    // cache of linked functions
+       {
+         ptr = funclist[i];
+         if (! strcmp (curr_func.src, ptr->src)) // cached function found
+         {
+           curr_func.handle = ptr->handle;
+           curr_func.proc   = ptr->proc;
+           curr_func.init   = ptr->init;
+           curr_func.tstamp = ptr->tstamp;
+           curr_func_no = i;
+#ifdef DEBUG
+           printf ("BlendProgram::process_buffer cached function %s found: %d\n   timestamp %s",
+                   ptr->src, curr_func_no, ctime(&ptr->tstamp));
+#endif
+           break;
+         }                                     // if cached function found
+       }                                       // for funclist.total
+      }                                                // if function name not empty
+    }                                          // if function changed
+  }                                            // if load_configuration()
+
+  // Now ensure that function binary is up to date, evtl recompile/relink it
+  if (curr_func.src[0])                                // current function not empty
+  {
+    if (curr_func.tstamp == -1 || BlendProgramTstamp > curr_func.tstamp)
+    {          // function first seen or linked before last config change
+      char str[BCTEXTLEN*2], dir[BCTEXTLEN], path[BCTEXTLEN];
+      time_t tstamp = -1;
+
+      // Evtl make function path absolute by prepending project path to it
+      strcpy (path, curr_func.src);
+      if (path[0] != '/') // path is relative, prepend current project path
+      {
+       strcpy (dir, server->mwindow->session->filename);
+       if (dir[0])
+       {
+         char *cp = strrchr (dir, '/');
+         if (cp)
+         {
+           cp[1] = 0;          // strip project filename off from project path
+           strcat (dir, path); // concatenate obtained path with function name
+           strcpy (path, dir);
+         }
+       }
+      }
+
+      // Try to lock function filename against concurrent compiler runs
+      // This kind of locking seems definitely reasonable here
+      struct flock locks;
+      locks.l_whence = SEEK_SET;
+      locks.l_start = locks.l_len = 0;
+      int fd = open (path, O_RDWR);
+      if (fd > -1)             // if lock cannot be set, ignore this for now
+      {
+       locks.l_type = F_WRLCK;
+       fcntl (fd, F_SETLKW, &locks); // try to wait for lock, ignoring errors
+       sprintf(str, "\"%s/dlfcn/BlendProgramCompile.pl\" \"%s\"",
+               getenv("CIN_DAT"), path);
+#ifdef DEBUG
+       printf ("BlendProgram::process_buffer\n   curr_func.tstamp %s",
+               ctime(&curr_func.tstamp));
+       printf ("   global tstamp %s   executing %s\n",
+               ctime(&BlendProgramTstamp), str);
+#endif
+       system (str);   // evtl recompile function if source newer than object
+       if (path[0] == '/') sprintf (str, "%s.so", path);
+       else sprintf (str, "./%s.so", path);    // dlopen requires a slash
+       struct stat statbuf;
+       if (0 == stat (str, &statbuf)) tstamp = statbuf.st_mtime;
+      }
+      else                             // function source cannot be opened
+      {
+#ifdef DEBUG
+       printf ("BlendProgram::process_buffer cannot access function %s\n",
+               curr_func.src);
+#endif
+      }
+
+      // Now test if function relinking needed, make function cache consistent
+      if (tstamp == -1)
+      {                // either function does not exist or compilation unsuccessful
+       if (fd > -1)
+         eprintf (_("Blend Program: compilation of program %s failed\nsee console printout for diagnostics\n"),
+                  curr_func.src);
+       if (curr_func_no >= 0)
+       {                                               // detach old function
+         if (funclist[curr_func_no]->handle)
+         {
+#ifdef DEBUG
+           printf ("BlendProgram::process_buffer detaching function %d dlclose(%s)\n",
+                   curr_func_no, funclist[curr_func_no]->src);
+#endif
+           dlclose (funclist[curr_func_no]->handle);
+         }
+#ifdef DEBUG
+         printf ("BlendProgram::process_buffer removing function %d (%s)\n",
+                 curr_func_no, curr_func.src);
+#endif
+         funclist[curr_func_no]->src[0] = 0;
+         funclist[curr_func_no]->handle = 0;
+         funclist[curr_func_no]->proc   = 0;
+         funclist[curr_func_no]->init   = 0;
+         funclist[curr_func_no]->tstamp = -1;
+         funclist.remove_object_number (curr_func_no);
+         curr_func_no = -1;
+       }
+       curr_func.handle = 0;
+       curr_func.proc   = 0;
+       curr_func.init   = 0;
+       curr_func.tstamp = time (NULL);
+      }
+      else if (curr_func.tstamp == -1 || tstamp > curr_func.tstamp)
+      {                                // function first seen or edited after linkage
+#ifdef DEBUG
+       printf ("BlendProgram::process_buffer\n   curr_func.tstamp %s",
+               ctime(&curr_func.tstamp));
+       printf ("   tstamp %s   relinking %s\n", ctime(&tstamp), str);
+#endif
+       if (curr_func_no >= 0 && funclist[curr_func_no]->handle)
+       {                                               // detach old function
+#ifdef DEBUG
+         printf ("BlendProgram::process_buffer detaching function %d dlclose(%s)\n",
+                 curr_func_no, funclist[curr_func_no]->src);
+#endif
+         dlclose (funclist[curr_func_no]->handle);
+         funclist[curr_func_no]->src[0] = 0;
+         funclist[curr_func_no]->handle = 0;
+         funclist[curr_func_no]->proc   = 0;
+         funclist[curr_func_no]->init   = 0;
+         funclist[curr_func_no]->tstamp = -1;
+       }
+       curr_func.proc   = 0;
+       curr_func.init   = 0;
+       curr_func.handle = dlopen (str, RTLD_NOW);      // shared object handle
+#ifdef DEBUG
+       printf ("BlendProgram::process_buffer dlopen(%s)=%p\n",
+               str, curr_func.handle);
+#endif
+       if (curr_func.handle)   // inquire necessary extern entry points
+       {                       // bpProc is mandatory, bpInit optional
+         curr_func.init = (BPF_init) dlsym (curr_func.handle, "bpInit");
+         if (curr_func.init == NULL)   // not a problem, we can continue
+           printf (_("Blend Program: optional entry point \"bpInit\" for program %s not found:\n%s\n"),
+                   str, dlerror());
+         curr_func.proc = (BPF_proc) dlsym (curr_func.handle, "bpProc");
+#ifdef DEBUG
+         printf ("BlendProgram::process_buffer dlsym(%s) init=%p proc=%p\n",
+                 curr_func.src, curr_func.init, curr_func.proc);
+#endif
+         if (curr_func.proc == NULL)   // nothing to do if this not working
+         {
+           eprintf (_("Blend Program: entry point \"bpProc\" for program %s not found:\n%s\n"),
+                    str, dlerror());
+#ifdef DEBUG
+           printf ("BlendProgram::process_buffer dlclose(%s)\n",
+                   curr_func.src);
+#endif
+           dlclose (curr_func.handle);
+           curr_func.handle = 0;
+           curr_func.init   = 0;
+         }
+       }
+       else
+         eprintf (_("Blend Program: dynamic load of program %s failed:\n%s\n"),
+                  str, dlerror());
+       curr_func.tstamp = time (NULL);
+       if (curr_func_no >= 0)          // function was already in cache
+       {
+         if (curr_func.proc)                   // update object in cache
+         {
+           strcpy (funclist[curr_func_no]->src, curr_func.src);
+           funclist[curr_func_no]->handle = curr_func.handle;
+           funclist[curr_func_no]->proc   = curr_func.proc;
+           funclist[curr_func_no]->init   = curr_func.init;
+           funclist[curr_func_no]->tstamp = curr_func.tstamp;
+#ifdef DEBUG
+           printf ("BlendProgram::process_buffer function %d (%s) updated\n   timestamp %s",
+                   curr_func_no, curr_func.src, ctime(&curr_func.tstamp));
+#endif
+         }
+         else                                  // remove outdated function
+         {
+#ifdef DEBUG
+           printf ("BlendProgram::process_buffer removing function %d (%s)\n",
+                   curr_func_no, curr_func.src);
+#endif
+           funclist.remove_object_number (curr_func_no);
+           curr_func_no = -1;
+         }
+       }
+       else if (curr_func.proc)        // add new linked function to cache
+       {
+         curr_func_no = funclist.total;
+         ptr = new BlendProgramFunc;
+         funclist.append (ptr);
+         strcpy (ptr->src, curr_func.src);
+         ptr->handle = curr_func.handle;
+         ptr->proc   = curr_func.proc;
+         ptr->init   = curr_func.init;
+         ptr->tstamp = curr_func.tstamp;
+#ifdef DEBUG
+         printf ("BlendProgram::process_buffer function %d (%s) appended\n   timestamp %s",
+                 curr_func_no, ptr->src, ctime(&ptr->tstamp));
+#endif
+       }                               // if function in cache
+      }
+      else                             // function does not need relinking
+      {
+       curr_func.tstamp = time (NULL);
+       if (curr_func_no >= 0)                  // just update timestamp
+         funclist[curr_func_no]->tstamp = curr_func.tstamp;
+#ifdef DEBUG
+       printf ("BlendProgram::process_buffer function %s does not need relinking\n   cache number %d timestamp %s",
+               curr_func.src, curr_func_no, ctime(&curr_func.tstamp));
+#endif
+      }                                                // if tstamp
+
+      if (fd > -1)                             // unlock function
+      {
+       locks.l_type = F_UNLCK;
+       fcntl (fd, F_SETLK, &locks);
+       close (fd);
+      }
+    }                                  // if function first seen or changed
+  }                                    // if current function not empty
+
+  func_lock->unlock();         // end mocking up with function shared object
+
+  // Now prepare the important pars and read all involved frames...
+  layers = get_total_buffers();
+  width  = frame[0]->get_w();
+  height = frame[0]->get_h();
+  for (int l=0; l<layers; l++)
+    read_frame (frame[l], l, start_position, frame_rate, 0);
+  this->frame = frame;
+
+  if (curr_func.proc == NULL) return 0;                // no function, nothing to do
+
+  color_proj = frame[0]->get_color_model(); // internal colorspace of project
+  if (color_proj == BC_RGBA_FLOAT ||
+      color_proj == BC_RGBA8888   ||
+      color_proj == BC_YUVA8888)
+    has_alpha = 1;                     // has alpha channel
+  else has_alpha = 0;
+  color_work = config.colorspace;      // will be requested from the function
+  int color_arg  = color_work;
+  int min_layers = layers;             // function's min required no of tracks
+  int parallel   = 0;                  // assumed not parallelized by default
+  if (curr_func.init != NULL)          // ask function about important pars
+    curr_func.init (&color_arg, color_proj, &min_layers, layers,
+                   &parallel, config.parallel, width, height, has_alpha);
+  if (min_layers > layers)
+  {
+    if (refresh_eprintf)
+      eprintf (_("Blend Program: cannot execute program %s:\nrequires %d tracks to process, has only %d tracks\n"),
+              curr_func.src, min_layers, layers);
+    return 0;                          // too few tracks to do anything
+  }
+  if (color_work == BlendProgramConfig::AUTO) color_work = color_arg;
+  if (color_work == BlendProgramConfig::AUTO)
+    color_work = BlendProgramConfig::PROJECT; // still not defined, dont change
+  if (! config.parallel) parallel = 0;         // parallelism not requested
+
+  // In case of a fatal bug in the user defined function (SIGFPE, SIGSEGV,...)
+  // Cinelerra perhaps will crash. Unfortunately we cannot handle the signals
+  // here: signal handler as set by sigaction() is process-wide, the same
+  // for all threads. And Cinelerra already uses its own signal handler
+  // for debug purposes which we are not allowed to overwrite with our one.
+  // Infinities and NaN will be trapped and substituted with configured color.
+  // Here we prepare key color components in three possible color spaces
+  // from this configured color. Used to substitute NaN or infinities.
+  // Can be used also inside user's function like a chroma key.
+  rgb_r = config.red;          // user's configured color is always RGB
+  rgb_g = config.green;
+  rgb_b = config.blue;
+  YUV::yuv.rgb_to_yuv_f (rgb_r, rgb_g, rgb_b, yuv_y, yuv_u, yuv_v); // make YUV
+  HSV::rgb_to_hsv       (rgb_r, rgb_g, rgb_b, hsv_h, hsv_s, hsv_v); // make HSV
+  key_a = config.alpha;                // user's configured alpha
+
+  if (parallel)                // parallelism desired, and supoported by the function
+  {
+    if (! engine)
+      engine = new BlendProgramEngine (this,
+                                      get_project_smp() + 1,
+                                      get_project_smp() + 1);
+    engine->process_packages();
+  }
+  else process_frames (0, height);     // process everything sequential
+
+  return 0;                                            // WHEW !!!
+}
+
+// Now comes the whole math. User's function will get everything in float.
+// If the project's color model is 8-bit, pixels will be converted to float.
+// Then, if requested, pixels will be converted to working color space
+// (RGB, YUV, or HSV) which is required by the function. After processing,
+// all the conversions will be rolled back in the reverse order.
+// This universal function is called via loadbalance multithreading engine
+// as well as directly if parallelism not requested or not supported
+
+void BlendProgram::process_frames (int y1, int y2)
+{
+  float r[layers], g[layers], b[layers], a[layers];
+  float rk, gk, bk, yk, uk, vk;
+  int k, l, start, step;
+
+  if (config.direction == BlendProgramConfig::BOTTOM_FIRST)
+  {
+    start = layers-1;
+    step  = -1;
+  }
+  else
+  {
+    start = 0;
+    step  = 1;
+  }
+
+  int clip_colors = config.clipcolors;         // clipping floats is optional
+  yk = uk = vk = 0;                            // to make gcc -O2 happy
+
+  switch (color_proj)
+  {
+  case BC_RGB_FLOAT:           // RGB  [ 0 .. 1 ], out of bounds possible
+  case BC_RGBA_FLOAT:          // RGBA [ 0 .. 1 ], out of bounds possible
+    for (int i=y1; i<y2; i++)          // scan all rows
+    {
+      float *row[layers];
+      for (l=0; l<layers; l++) row[l] = (float *)frame[l]->get_rows()[i];
+      for (int j=0; j<width; j++)      // scan all pixels
+      {
+       k = start;
+       for (l=0; l<layers; l++)        // convert source frames to args
+       {
+         if (color_work == BlendProgramConfig::YUV)
+         {
+           YUV::yuv.rgb_to_yuv_f (row[l][0], row[l][1], row[l][2],
+                                  r[k],        // Y pixel to blend
+                                  g[k],        // U pixel
+                                  b[k]);       // V pixel
+           yk = yuv_y;                         // user's key color (YUV)
+           uk = yuv_u;
+           vk = yuv_v;
+         }
+         else if (color_work == BlendProgramConfig::HSV)
+         {
+           HSV::rgb_to_hsv (row[l][0], row[l][1], row[l][2],
+                            r[k],              // H pixel to blend
+                            g[k],              // S pixel
+                            b[k]);             // V pixel
+           yk = hsv_h;                         // user's key color (HSV)
+           uk = hsv_s;
+           vk = hsv_v;
+         }
+         else  // either RGB or by PROJECT, no change
+         {
+           r[k] = row[l][0];                   // RGB pixel to blend
+           g[k] = row[l][1];
+           b[k] = row[l][2];
+           yk   = rgb_r;                       // user's key color (RGB)
+           uk   = rgb_g;
+           vk   = rgb_b;
+         }     // if color_work
+         a[k] = has_alpha ? row[l][3] : 1;
+         k += step;
+       }       // scan tracks for l = 0 .. layers
+       curr_func.proc (layers, r, g, b, a, yk, uk, vk, key_a,
+                       j, i, width, height, has_alpha); // call user function
+       k = start;
+       for (l=0; l<layers; l++)        // convert modified args back to frames
+       {
+         if (clip_colors) CLAMP (a[k], 0, 1);
+         if (color_work == BlendProgramConfig::YUV)
+         {
+           if (clip_colors)
+           {
+             CLAMP (r[k],  0,   1);
+             CLAMP (g[k], -0.5, 0.5);
+             CLAMP (b[k], -0.5, 0.5);
+           }
+           if (! (isfinite(r[k]) && isfinite(g[k]) &&
+                  isfinite(b[k]) && isfinite(a[k])))
+           {   // substitute NaN or unclipped infinity with user's color (YUV)
+             r[k] = yuv_y;
+             g[k] = yuv_u;
+             b[k] = yuv_v;
+             a[k] = key_a;
+           }
+           YUV::yuv.yuv_to_rgb_f (row[l][0], row[l][1], row[l][2],
+                                  r[k],        // Y
+                                  g[k],        // U
+                                  b[k]);       // V
+         }
+         else if (color_work == BlendProgramConfig::HSV)
+         {
+           if (clip_colors)
+           {
+             if (isfinite(r[k]) && (r[k] < 0 || r[k] >= 360))
+               r[k] -= floor(r[k]/360)*360;    // cannot clamp infinity here
+             CLAMP (g[k], 0, 1);
+             CLAMP (b[k], 0, 1);
+           }
+           if (! (isfinite(r[k]) && isfinite(g[k]) &&
+                  isfinite(b[k]) && isfinite(a[k])))
+           {   // substitute NaN or unclipped infinity with user's color (HSV)
+             r[k] = hsv_h;
+             g[k] = hsv_s;
+             b[k] = hsv_v;
+             a[k] = key_a;
+           }
+           HSV::hsv_to_rgb (row[l][0], row[l][1], row[l][2],
+                            r[k],              // H
+                            g[k],              // S
+                            b[k]);             // V
+         }
+         else  // either RGB or by PROJECT, no change, clip only
+         {
+           if (clip_colors)
+           {
+             CLAMP (r[k], 0, 1);
+             CLAMP (g[k], 0, 1);
+             CLAMP (b[k], 0, 1);
+           }
+           if (! (isfinite(r[k]) && isfinite(g[k]) &&
+                  isfinite(b[k]) && isfinite(a[k])))
+           {   // substitute NaN or unclipped infinity with user's color (RGB)
+             r[k] = rgb_r;
+             g[k] = rgb_g;
+             b[k] = rgb_b;
+             a[k] = key_a;
+           }
+           row[l][0] = r[k];
+           row[l][1] = g[k];
+           row[l][2] = b[k];
+         }     // if color_work
+         if (! has_alpha)                      // evtl simulate alpha channel
+         {
+           row[l][0] *= a[k];
+           row[l][1] *= a[k];
+           row[l][2] *= a[k];
+         }
+         if (has_alpha)                        // store real alpha channel
+         {
+           row[l][3] = a[k];
+           row[l] += 4;
+         }
+         else row[l] += 3;                     // no alpha channel
+         k += step;
+       }       // scan tracks for l = 0 .. layers
+      }                // scan pixels for j = 0 .. width
+    }          // scan rows for i = y1 .. y2
+    break;
+  case BC_RGB888:              // RGB  [ 0 .. 1 ], must be in bounds
+  case BC_RGBA8888:            // RGBA [ 0 .. 1 ], must be in bounds
+    for (int i=y1; i<y2; i++)          // scan all rows
+    {
+      unsigned char *row[layers];
+      for (l=0; l<layers; l++)
+       row[l] = (unsigned char *)frame[l]->get_rows()[i];
+      for (int j=0; j<width; j++)      // scan all pixels
+      {
+       k = start;
+       for (l=0; l<layers; l++)        // convert source frames to args
+       {
+         if (color_work == BlendProgramConfig::YUV)
+         {
+           YUV::yuv.rgb_to_yuv_f ((float)row[l][0]/255,
+                                  (float)row[l][1]/255,
+                                  (float)row[l][2]/255,
+                                  r[k],        // Y pixel to blend
+                                  g[k],        // U pixel
+                                  b[k]);       // V pixel
+           yk = yuv_y;                         // user's key color (YUV)
+           uk = yuv_u;
+           vk = yuv_v;
+         }
+         else if (color_work == BlendProgramConfig::HSV)
+         {
+           HSV::rgb_to_hsv ((float)row[l][0]/255,
+                            (float)row[l][1]/255,
+                            (float)row[l][2]/255,
+                            r[k],              // H pixel to blend
+                            g[k],              // S pixel
+                            b[k]);             // V pixel
+           yk = hsv_h;                         // user's key color (HSV)
+           uk = hsv_s;
+           vk = hsv_v;
+         }
+         else  // either RGB or by PROJECT, conversion to float only
+         {
+           r[k] = (float)row[l][0]/255;        // RGB pixel to blend
+           g[k] = (float)row[l][1]/255;
+           b[k] = (float)row[l][2]/255;
+           yk   = rgb_r;                       // user's key color (RGB)
+           uk   = rgb_g;
+           vk   = rgb_b;
+         }     // if color_work
+         a[k] = has_alpha ? (float)row[l][3]/255 : 1;
+         k += step;
+       }       // scan tracks for l = 0 .. layers
+       curr_func.proc (layers, r, g, b, a, yk, uk, vk, key_a,
+                       j, i, width, height, has_alpha); // call user function
+       k = start;
+       for (l=0; l<layers; l++)        // convert modified args back to frames
+       {
+         if (clip_colors) CLAMP (a[k], 0, 1);
+         if (color_work == BlendProgramConfig::YUV)
+         {
+           if (clip_colors)
+           {
+             CLAMP (r[k],  0,   1);
+             CLAMP (g[k], -0.5, 0.5);
+             CLAMP (b[k], -0.5, 0.5);
+           }
+           if (! (isfinite(r[k]) && isfinite(g[k]) &&
+                  isfinite(b[k]) && isfinite(a[k])))
+           {   // substitute NaN or unclipped infinity with user's color (YUV)
+             r[k] = yuv_y;
+             g[k] = yuv_u;
+             b[k] = yuv_v;
+             a[k] = key_a;
+           }
+           YUV::yuv.yuv_to_rgb_f (rk, gk, bk,
+                                  r[k],        // Y
+                                  g[k],        // U
+                                  b[k]);       // V
+         }
+         else if (color_work == BlendProgramConfig::HSV)
+         {
+           if (clip_colors)
+           {
+             if (isfinite(r[k]) && (r[k] < 0 || r[k] >= 360))
+               r[k] -= floor(r[k]/360)*360;    // cannot clamp infinity here
+             CLAMP (g[k], 0, 1);
+             CLAMP (b[k], 0, 1);
+           }
+           if (! (isfinite(r[k]) && isfinite(g[k]) &&
+                  isfinite(b[k]) && isfinite(a[k])))
+           {   // substitute NaN or unclipped infinity with user's color (HSV)
+             r[k] = hsv_h;
+             g[k] = hsv_s;
+             b[k] = hsv_v;
+             a[k] = key_a;
+           }
+           HSV::hsv_to_rgb (rk, gk, bk,
+                            r[k],              // H
+                            g[k],              // S
+                            b[k]);             // V
+         }
+         else  // either RGB or by PROJECT, no change, clip only
+         {
+           if (clip_colors)
+           {
+             CLAMP (r[k], 0, 1);
+             CLAMP (g[k], 0, 1);
+             CLAMP (b[k], 0, 1);
+           }
+           if (! (isfinite(r[k]) && isfinite(g[k]) &&
+                  isfinite(b[k]) && isfinite(a[k])))
+           {   // substitute NaN or unclipped infinity with user's color (RGB)
+             r[k] = rgb_r;
+             g[k] = rgb_g;
+             b[k] = rgb_b;
+             a[k] = key_a;
+           }
+           rk = r[k];
+           gk = g[k];
+           bk = b[k];
+         }     // if color_work
+         if (! has_alpha)                      // evtl simulate alpha channel
+         {
+           rk *= a[k];
+           gk *= a[k];
+           bk *= a[k];
+         }
+         row[l][0] = (unsigned char) CLIP (rk*255, 0, 255); // reformat / clip
+         row[l][1] = (unsigned char) CLIP (gk*255, 0, 255);
+         row[l][2] = (unsigned char) CLIP (bk*255, 0, 255);
+         if (has_alpha)                        // store real alpha channel
+         {
+           row[l][3] = (unsigned char) CLIP (a[k]*255, 0, 255);
+           row[l] += 4;
+         }
+         else row[l] += 3;                     // no alpha channel
+         k += step;
+       }       // scan tracks for l = 0 .. layers
+      }                // scan pixels for j = 0 .. width
+    }          // scan rows for i = y1 .. y2
+    break;
+  case BC_YUV888:      // R  [ 0 .. 1 ], GB [ -0.5 .. 0.5 ], must be in bounds
+  case BC_YUVA8888:    // RA [ 0 .. 1 ], GB [ -0.5 .. 0.5 ], must be in bounds
+    for (int i=y1; i<y2; i++)          // scan all rows
+    {
+      unsigned char *row[layers];
+      for (l=0; l<layers; l++)
+       row[l] = (unsigned char *)frame[l]->get_rows()[i];
+      for (int j=0; j<width; j++)      // scan all pixels
+      {
+       k = start;
+       for (l=0; l<layers; l++)        // convert source frames to args
+       {
+         if (color_work == BlendProgramConfig::RGB)
+         {
+           YUV::yuv.yuv_to_rgb_f (r[k], g[k], b[k], // RGB pixel to blend
+                                  (float)row[l][0]/255,
+                                  ((float)row[l][1]-128)/256,
+                                  ((float)row[l][2]-128)/256);
+           yk = rgb_r;                         // user's key color (RGB)
+           uk = rgb_g;
+           vk = rgb_b;
+         }
+         else if (color_work == BlendProgramConfig::HSV)
+         {
+           YUV::yuv.yuv_to_rgb_f (rk, gk, bk,  // RGB temporary pixel
+                                  (float)row[l][0]/255,
+                                  ((float)row[l][1]-128)/256,
+                                  ((float)row[l][2]-128)/256);
+           HSV::rgb_to_hsv (rk, gk, bk,
+                            r[k],              // H pixel to blend
+                            g[k],              // S pixel
+                            b[k]);             // V pixel
+           yk = hsv_h;                         // user's key color (HSV)
+           uk = hsv_s;
+           vk = hsv_v;
+         }
+         else  // either YUV or by PROJECT, conversion to float only
+         {
+           r[k] =  (float)row[l][0]/255;       // Y pixel to blend
+           g[k] = ((float)row[l][1]-128)/256;  // U pixel
+           b[k] = ((float)row[l][2]-128)/256;  // V pixel
+           yk = yuv_y;                         // user's key color (YUV)
+           uk = yuv_u;
+           vk = yuv_v;
+         }     // if color_work
+         a[k] = has_alpha ? (float)row[l][3]/255 : 1;
+         k += step;
+       }       // scan tracks for l = 0 .. layers
+       curr_func.proc (layers, r, g, b, a, yk, uk, vk, key_a,
+                       j, i, width, height, has_alpha); // call user function
+       k = start;
+       for (l=0; l<layers; l++)        // convert modified args back to frames
+       {
+         if (clip_colors) CLAMP (a[k], 0, 1);
+         if (color_work == BlendProgramConfig::RGB)
+         {
+           if (clip_colors)
+           {
+             CLAMP (r[k], 0, 1);
+             CLAMP (g[k], 0, 1);
+             CLAMP (b[k], 0, 1);
+           }
+           if (! (isfinite(r[k]) && isfinite(g[k]) &&
+                  isfinite(b[k]) && isfinite(a[k])))
+           {   // substitute NaN or unclipped infinity with user's color (RGB)
+             r[k] = rgb_r;
+             g[k] = rgb_g;
+             b[k] = rgb_b;
+             a[k] = key_a;
+           }
+           YUV::yuv.rgb_to_yuv_f (r[k], g[k], b[k], yk, uk, vk);
+         }
+         else if (color_work == BlendProgramConfig::HSV)
+         {
+           if (clip_colors)
+           {
+             if (isfinite(r[k]) && (r[k] < 0 || r[k] >= 360))
+               r[k] -= floor(r[k]/360)*360;    // cannot clamp infinity here
+             CLAMP (g[k], 0, 1);
+             CLAMP (b[k], 0, 1);
+           }
+           if (! (isfinite(r[k]) && isfinite(g[k]) &&
+                  isfinite(b[k]) && isfinite(a[k])))
+           {   // substitute NaN or unclipped infinity with user's color (HSV)
+             r[k] = hsv_h;
+             g[k] = hsv_s;
+             b[k] = hsv_v;
+             a[k] = key_a;
+           }                     //     H     S     V
+           HSV::hsv_to_rgb (rk, gk, bk, r[k], g[k], b[k]);
+           if (clip_colors)
+           {
+             CLAMP (r[k], 0, 1);
+             CLAMP (g[k], 0, 1);
+             CLAMP (b[k], 0, 1);
+           }
+           YUV::yuv.rgb_to_yuv_f (rk, gk, bk, yk, uk, vk);
+         }
+         else  // either YUV or by PROJECT, no change, clip only
+         {
+           if (clip_colors)
+           {
+             CLAMP (r[k],  0,   1);
+             CLAMP (g[k], -0.5, 0.5);
+             CLAMP (b[k], -0.5, 0.5);
+           }
+           if (! (isfinite(r[k]) && isfinite(g[k]) &&
+                  isfinite(b[k]) && isfinite(a[k])))
+           {   // substitute NaN or unclipped infinity with user's color (YUV)
+             r[k] = yuv_y;
+             g[k] = yuv_u;
+             b[k] = yuv_v;
+             a[k] = key_a;
+           }
+           yk = r[k];
+           uk = g[k];
+           vk = b[k];
+         }     // if color_work
+         if (! has_alpha)                      // evtl simulate alpha channel
+         {
+           yk *= a[k];
+           uk *= a[k];
+           vk *= a[k];
+         }
+         row[l][0] = (unsigned char) CLIP (yk*255,       0, 255); // reformat
+         row[l][1] = (unsigned char) CLIP ((uk+0.5)*256, 0, 255);
+         row[l][2] = (unsigned char) CLIP ((vk+0.5)*256, 0, 255);
+         if (has_alpha)                        // store real alpha channel
+         {
+           row[l][3] = (unsigned char) CLIP (a[k]*255, 0, 255);
+           row[l] += 4;
+         }
+         else row[l] += 3;                     // no alpha channel
+         k += step;
+       }       // scan tracks for l = 0 .. layers
+      }                // scan pixels for j = 0 .. width
+    }          // scan rows for i = y1 .. y2
+    break;
+  default:
+    break;
+  }            // switch color_proj
+}                                              // WHEW !!!
+
+void BlendProgram::save_data(KeyFrame *keyframe)
+{
+  FileXML output;
+
+  output.set_shared_output(keyframe->xbuf);
+
+  output.tag.set_title("BLEND_PROGRAM");
+  output.tag.set_property("FUNCNAME",   config.funcname);
+  output.tag.set_property("PARALLEL",   config.parallel);
+  output.tag.set_property("DIRECTION",  config.direction);
+  output.tag.set_property("COLORSPACE", config.colorspace);
+  output.tag.set_property("CLIPCOLORS", config.clipcolors);
+  output.tag.set_property("RED",        config.red);
+  output.tag.set_property("GREEN",      config.green);
+  output.tag.set_property("BLUE",       config.blue);
+  output.tag.set_property("ALPHA",      config.alpha);
+  output.append_tag();
+  output.tag.set_title("/BLEND_PROGRAM");
+  output.append_tag();
+  output.append_newline();
+  output.terminate_string();
+}
+
+void BlendProgram::read_data(KeyFrame *keyframe)
+{
+  FileXML input;
+
+  input.set_shared_input(keyframe->xbuf);
+
+  while(!input.read_tag())
+  {
+    if(input.tag.title_is("BLEND_PROGRAM"))
+    {
+      input.tag.get_property("FUNCNAME", config.funcname);
+      config.parallel  = input.tag.get_property("PARALLEL",  config.parallel);
+      config.direction = input.tag.get_property("DIRECTION", config.direction);
+      config.colorspace =
+       input.tag.get_property("COLORSPACE", config.colorspace);
+      config.clipcolors =
+       input.tag.get_property("CLIPCOLORS", config.clipcolors);
+      config.red   = input.tag.get_property("RED",   config.red);
+      config.green = input.tag.get_property("GREEN", config.green);
+      config.blue  = input.tag.get_property("BLUE",  config.blue);
+      config.alpha = input.tag.get_property("ALPHA", config.alpha);
+    }
+  }
+}
+
+void BlendProgram::update_gui()
+{
+  if( ! thread ) return;
+  if( ! (load_configuration() || inspect_configuration) ) return;
+  inspect_configuration = 0;   // update once after change or after creation
+  thread->window->lock_window("BlendProgram::update_gui");
+  BlendProgramWindow *window = (BlendProgramWindow*)thread->window;
+
+  window->funcname->update(config.funcname);
+  window->parallel->update(config.parallel);
+  window->clipcolors->update(config.clipcolors);
+  window->direction->set_text(
+    BlendProgramConfig::direction_to_text(config.direction));
+  window->colorspace->set_text(
+    BlendProgramConfig::colorspace_to_text(config.colorspace));
+  window->update_key_sample();
+  window->alpha_text->update(config.alpha);
+  window->key_alpha->update(config.alpha);
+
+  thread->window->unlock_window();
+}
+
+////////////////////////////////////////////
+// Multithreaded processing stuff
+////////////////////////////////////////////
+
+BlendProgramEngine::BlendProgramEngine(BlendProgram *plugin, 
+                                      int total_clients, 
+                                      int total_packages)
+  : LoadServer(total_clients, total_packages)
+{
+  this->plugin = plugin;
+}
+
+void BlendProgramEngine::init_packages ()
+{
+  for (int i=0; i<get_total_packages(); i++)
+  {
+    BlendProgramPackage *pkg = (BlendProgramPackage *) get_package (i);
+    pkg->y1 = plugin->height *  i      / get_total_packages ();
+    pkg->y2 = plugin->height * (i + 1) / get_total_packages ();
+  }
+}
+
+LoadClient *BlendProgramEngine::new_client ()
+{
+  return new BlendProgramUnit (plugin, this);
+}
+
+LoadPackage *BlendProgramEngine::new_package ()
+{
+  return new BlendProgramPackage;
+}
+
+BlendProgramPackage::BlendProgramPackage()
+  : LoadPackage()
+{
+}
+
+BlendProgramUnit::BlendProgramUnit (BlendProgram *plugin,
+                                   BlendProgramEngine *engine)
+  : LoadClient (engine)
+{
+  this->plugin = plugin;
+  this->engine = engine;
+}
+
+void BlendProgramUnit::process_package(LoadPackage *package)
+{
+  BlendProgramPackage *pkg = (BlendProgramPackage *) package;
+
+  plugin->process_frames (pkg->y1, pkg->y2);
+}
diff --git a/cinelerra-5.1/plugins/blendprogram/blendprogram.h b/cinelerra-5.1/plugins/blendprogram/blendprogram.h
new file mode 100644 (file)
index 0000000..1264658
--- /dev/null
@@ -0,0 +1,430 @@
+/*
+ * CINELERRA
+ * Copyright (C) 2008 Adam Williams <broadcast at earthling dot net>
+ *
+ * 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
+ *
+ */
+
+#ifndef BLENDPROGRAM_H
+#define BLENDPROGRAM_H
+
+#include "guicast.h"
+#include "loadbalance.h"
+#include "colorpicker.h"
+#include "pluginvclient.h"
+
+// Several forward declarations
+
+class BlendProgram;
+class BlendProgramConfig;
+class BlendProgramWindow;
+class BlendProgramAlphaText;
+class BlendProgramAlphaSlider;
+class BlendProgramFileBox;
+
+// Plugin configuration class definition
+
+class BlendProgramConfig
+{
+public:
+  BlendProgramConfig();
+
+  int equivalent(BlendProgramConfig &that);
+  void copy_from(BlendProgramConfig &that);
+  void interpolate(BlendProgramConfig &prev,
+                  BlendProgramConfig &next,
+                  int64_t prev_frame,
+                  int64_t next_frame,
+                  int64_t current_frame);
+
+  int get_key_color();
+
+  char funcname[BCTEXTLEN];
+  int parallel;
+  int clipcolors;
+
+  static const char *direction_to_text(int direction);
+  int direction;
+  enum
+  {
+    BOTTOM_FIRST,
+    TOP_FIRST
+  };
+
+  static const char *colorspace_to_text(int colorspace);
+  int colorspace;
+  enum
+  {
+    AUTO,                              // requested from function
+    RGB,
+    YUV,
+    HSV,
+    PROJECT                            // as defined in project settings
+  };
+
+  float red;                           // key color to substitute for NaN
+  float green;
+  float blue;
+  float alpha;                         // alpha to substitute for NaN
+};
+
+// Plugin dialog window class definition
+
+class BlendProgramFuncname : public BC_TextBox
+{
+public:
+  BlendProgramFuncname(BlendProgram *plugin, const char *funcname,
+                      BlendProgramWindow *gui, int x, int y);
+  int handle_event();
+  BlendProgram *plugin;
+  BlendProgramWindow *gui;
+};
+
+class BlendProgramDetach : public BC_GenericButton
+{
+public:
+  BlendProgramDetach(BlendProgram *plugin, BlendProgramWindow *gui,
+                    int x, int y);
+  int handle_event();
+  BlendProgram *plugin;
+  BlendProgramWindow *gui;
+};
+
+class BlendProgramEdit : public BC_GenericButton
+{
+public:
+  BlendProgramEdit(BlendProgram *plugin, BlendProgramWindow *gui,
+                  int x, int y);
+  int handle_event();
+  BlendProgram *plugin;
+  BlendProgramWindow *gui;
+};
+
+class BlendProgramRefresh : public BC_GenericButton
+{
+public:
+  BlendProgramRefresh(BlendProgram *plugin, BlendProgramWindow *gui,
+                     int x, int y);
+  int handle_event();
+  BlendProgram *plugin;
+  BlendProgramWindow *gui;
+};
+
+class BlendProgramToCurdir : public BC_GenericButton
+{
+public:
+  BlendProgramToCurdir(BlendProgramFileBox *file_box, int x, int y);
+  int handle_event();
+  BlendProgramFileBox *file_box;
+};
+
+class BlendProgramToUsrlib : public BC_GenericButton
+{
+public:
+  BlendProgramToUsrlib(BlendProgramFileBox *file_box, int x, int y);
+  int handle_event();
+  BlendProgramFileBox *file_box;
+};
+
+class BlendProgramToSyslib : public BC_GenericButton
+{
+public:
+  BlendProgramToSyslib(BlendProgramFileBox *file_box, int x, int y);
+  int handle_event();
+  BlendProgramFileBox *file_box;
+};
+
+class BlendProgramCopyCurdir : public BC_GenericButton
+{
+public:
+  BlendProgramCopyCurdir(BlendProgramFileBox *file_box, int x, int y);
+  int handle_event();
+  BlendProgramFileBox *file_box;
+};
+
+class BlendProgramCopyUsrlib : public BC_GenericButton
+{
+public:
+  BlendProgramCopyUsrlib(BlendProgramFileBox *file_box, int x, int y);
+  int handle_event();
+  BlendProgramFileBox *file_box;
+};
+
+class BlendProgramFileEdit : public BC_GenericButton
+{
+public:
+  BlendProgramFileEdit(BlendProgramFileBox *file_box, int x, int y);
+  int handle_event();
+  BlendProgramFileBox *file_box;
+};
+
+class BlendProgramFileBox : public BC_FileBox
+{
+public:
+  BlendProgramFileBox(BlendProgram *plugin, BlendProgramWindow *gui,
+                     char *init_path);
+  ~BlendProgramFileBox();
+  void add_objects();
+  int resize_event(int w, int h);
+
+  BlendProgram *plugin;
+  BlendProgramWindow *gui;
+  BlendProgramToCurdir *to_curdir;
+  BlendProgramToUsrlib *to_usrlib;
+  BlendProgramToSyslib *to_syslib;
+  BlendProgramCopyCurdir *copy_curdir;
+  BlendProgramCopyUsrlib *copy_usrlib;
+  BlendProgramFileEdit *file_edit;
+
+  int reinit_path;
+};
+
+class BlendProgramFileButton : public BC_GenericButton, public Thread
+{
+public:
+  BlendProgramFileButton(BlendProgram *plugin, BlendProgramWindow *gui,
+                        int x, int y);
+  ~BlendProgramFileButton();
+  int handle_event();
+  void run();
+  void stop();
+  BlendProgram *plugin;
+  BlendProgramWindow *gui;
+  BlendProgramFileBox *file_box;
+};
+
+class BlendProgramDirection : public BC_PopupMenu
+{
+public:
+  BlendProgramDirection(BlendProgram *plugin, int x, int y);
+  void create_objects();
+  int handle_event();
+  BlendProgram *plugin;
+};
+
+class BlendProgramColorspace : public BC_PopupMenu
+{
+public:
+  BlendProgramColorspace(BlendProgram *plugin, int x, int y);
+  void create_objects();
+  int handle_event();
+  BlendProgram *plugin;
+};
+
+class BlendProgramClipcolors : public BC_CheckBox
+{
+public:
+  BlendProgramClipcolors(BlendProgram *plugin, BlendProgramWindow *gui,
+                        int x, int y);
+  int handle_event();
+  BlendProgram *plugin;
+  BlendProgramWindow *gui;
+};
+
+class BlendProgramParallel : public BC_CheckBox
+{
+public:
+  BlendProgramParallel(BlendProgram *plugin, BlendProgramWindow *gui,
+                      int x, int y);
+  int handle_event();
+  BlendProgram *plugin;
+  BlendProgramWindow *gui;
+};
+
+class BlendProgramKeyColor : public BC_GenericButton
+{
+public:
+  BlendProgramKeyColor(BlendProgram *plugin, BlendProgramWindow *gui,
+                      int x, int y);
+  int handle_event();
+  BlendProgram *plugin;
+  BlendProgramWindow *gui;
+};
+
+class BlendProgramColorPicker : public BC_GenericButton
+{
+public:
+  BlendProgramColorPicker(BlendProgram *plugin, BlendProgramWindow *gui,
+                         int x, int y);
+  int handle_event();
+  BlendProgram *plugin;
+  BlendProgramWindow *gui;
+};
+
+class BlendProgramColorThread : public ColorPicker
+{
+public:
+  BlendProgramColorThread(BlendProgram *plugin, BlendProgramWindow *gui);
+  int handle_new_color(int output, int alpha);
+  BlendProgram *plugin;
+  BlendProgramWindow *gui;
+};
+
+class BlendProgramAlphaText : public BC_TumbleTextBox
+{
+public:
+  BlendProgramAlphaText(BlendProgram *plugin, BlendProgramWindow *gui,
+                       BlendProgramAlphaSlider *slider, int x, int y,
+                       float min, float max, float *output);
+  ~BlendProgramAlphaText();
+  int handle_event();
+  BlendProgram *plugin;
+  BlendProgramWindow *gui;
+  BlendProgramAlphaSlider *slider;
+  float *output;
+  float min, max;
+};
+
+class BlendProgramAlphaSlider : public BC_FSlider
+{
+public:
+  BlendProgramAlphaSlider(BlendProgram *plugin, BlendProgramAlphaText *text,
+                         int x, int y, int w, float min, float max,
+                         float *output);
+  ~BlendProgramAlphaSlider();
+  int handle_event();
+  BlendProgram *plugin;
+  BlendProgramAlphaText *text;
+  float *output;
+};
+
+class BlendProgramWindow : public PluginClientWindow
+{
+public:
+  BlendProgramWindow(BlendProgram *plugin);
+  ~BlendProgramWindow();
+
+  void create_objects();
+  void update_key_sample();
+  void done_event();
+  int close_event();
+  int hide_window(int flush=1);
+
+  BlendProgram *plugin;
+  BlendProgramFuncname *funcname;
+  BlendProgramDirection *direction;
+  BlendProgramColorspace *colorspace;
+  BlendProgramClipcolors *clipcolors;
+  BlendProgramParallel *parallel;
+  BC_SubWindow *key_sample;
+  BlendProgramKeyColor *key_color;
+  BlendProgramColorPicker *color_picker;
+  BlendProgramColorThread *color_thread;
+  BlendProgramAlphaText *alpha_text;
+  BlendProgramAlphaSlider *key_alpha;
+  BlendProgramFileButton *file_button;
+  BlendProgramDetach *detach_button;
+  BlendProgramEdit *edit_button;
+  BlendProgramRefresh *refresh_button;
+
+  Mutex *editing_lock;
+  int editing;
+};
+
+// For multithreading processing engine
+
+class BlendProgramEngine : public LoadServer
+{
+public:
+  BlendProgramEngine(BlendProgram *plugin,
+                    int total_clients, int total_packages);
+  void init_packages();
+  LoadClient* new_client();
+  LoadPackage* new_package();
+
+  BlendProgram *plugin;
+};
+
+class BlendProgramPackage : public LoadPackage
+{
+public:
+  BlendProgramPackage();
+
+  int y1, y2;
+};
+
+class BlendProgramUnit : public LoadClient
+{
+public:
+  BlendProgramUnit(BlendProgram *plugin, BlendProgramEngine *engine);
+  void process_package(LoadPackage *package);
+
+  BlendProgram *plugin;
+  BlendProgramEngine *engine;
+};
+
+// Plugin main class definition
+
+// User function prototypes, C style, processing and init entries
+typedef void (*BPF_proc) (int, float *, float *, float *, float *,
+                         float, float, float, float, int, int, int, int, int);
+typedef void (*BPF_init) (int *, int, int *, int, int *, int, int, int, int);
+
+class BlendProgramFunc
+{
+public:
+  BlendProgramFunc();
+  ~BlendProgramFunc();
+
+  char src[BCTEXTLEN];                         // source filename
+  void *handle;                                        // handle returned from dlopen()
+  BPF_proc proc;                // main processing entry from dlsym()
+  BPF_init init;               // optional initializing entry from dlsym()
+  time_t tstamp;               // timestamp when function was last linked
+};
+
+class BlendProgram : public PluginVClient
+{
+public:
+  BlendProgram(PluginServer *server);
+  ~BlendProgram();
+
+  PLUGIN_CLASS_MEMBERS(BlendProgramConfig);
+
+  int process_buffer(VFrame **frame, int64_t start_position, double frame_rate);
+  int is_realtime();
+  int is_multichannel();
+  int is_synthesis();
+  void save_data(KeyFrame *keyframe);
+  void read_data(KeyFrame *keyframe);
+  void update_gui();
+  void process_frames(int y1, int y2);
+
+  // this flag set in constructor, cleared after first load_configuration()
+  int inspect_configuration;
+
+  int layers;                          // no of tracks
+  int width, height;                   // frame dimensions
+  int color_proj;                      // project color model
+  int color_work;                      // working color space in the function
+  int has_alpha;                       // 1 == has alpha channel
+
+  // color components in three color spaces, used to substitute
+  // for NaN or infinity in invalid results, or as a chroma key
+  float rgb_r, rgb_g, rgb_b, yuv_y, yuv_u, yuv_v, hsv_h, hsv_s, hsv_v, key_a;
+
+  BlendProgramFunc curr_func;                  // currently active entry point
+  int curr_func_no;                            // no of current entry in list
+  ArrayList<BlendProgramFunc*> funclist;       // list of known entry points
+
+  VFrame **frame;                              // pointer to frames to process
+
+  Mutex *func_lock;
+
+  BlendProgramEngine *engine;                  // for parallelized processing
+};
+
+#endif /* BLENDPROGRAM_H */
diff --git a/cinelerra-5.1/plugins/blendprogram/chromakey.bp b/cinelerra-5.1/plugins/blendprogram/chromakey.bp
new file mode 100644 (file)
index 0000000..2d61ad9
--- /dev/null
@@ -0,0 +1,28 @@
+/***********************************************-*-C-*-**********/
+/*    Chromakey imitation program
+ * 1) Select suitable chromakey color via eg Compositor's color picker
+ * 2) Using opacity slider adjust hue deviation parameter for a best look
+ * For masking effect to be seen, either ensure placing an additional track
+ * filled with opaque background under the chromakeyed track,
+ * or use the background.bp blend program after chromakey.bp
+ *
+ * The formula below works as follows:
+ * Hue deviation from key > slider * 100: change nothing
+ * Hue deviation < slider * 100: multiply alpha by squared deviation/slider/100
+ * Where hue exactly equals key, alpha will be zeroed
+ *
+ * For another hue coefficient instead of 100, define COEFF correspondingly
+ */
+
+#define COEFF 100
+
+BLEND_PROGRAM_INIT
+
+COLORSPACE_HSV
+PARALLEL_SAFE
+
+BLEND_PROGRAM_PROC
+
+if (ABS(H(0)-KEY_H) < KEY_A*COEFF) A(0) *= SQR((H(0)-KEY_H)/KEY_A/COEFF);
+
+BLEND_PROGRAM_END