alpha \
bandslide \
bandwipe \
+ blendalgebra \
+ blendprogram \
bluebanana \
blur \
boxblur \
audioscope \
bandslide \
bandwipe \
+ blendalgebra \
+ blendprogram \
bluebanana \
blur \
boxblur \
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
--- /dev/null
+#!/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;
--- /dev/null
+/***********************************************-*-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 }
--- /dev/null
+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) *~
--- /dev/null
+/***********************************************-*-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
--- /dev/null
+/***********************************************-*-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
--- /dev/null
+/***********************************************-*-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
--- /dev/null
+/***********************************************-*-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
--- /dev/null
+/***********************************************-*-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
--- /dev/null
+/*
+ * 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,
+ ¶llel, 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);
+}
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/***********************************************-*-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
--- /dev/null
+/***********************************************-*-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
--- /dev/null
+/***********************************************-*-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
--- /dev/null
+/***********************************************-*-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
--- /dev/null
+/***********************************************-*-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
--- /dev/null
+/***********************************************-*-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
--- /dev/null
+/***********************************************-*-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
--- /dev/null
+/***********************************************-*-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
--- /dev/null
+/***********************************************-*-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
--- /dev/null
+/***********************************************-*-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
--- /dev/null
+/***********************************************-*-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
--- /dev/null
+/***********************************************-*-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
--- /dev/null
+/***********************************************-*-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
--- /dev/null
+/***********************************************-*-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
--- /dev/null
+/***********************************************-*-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
--- /dev/null
+/***********************************************-*-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
--- /dev/null
+/***********************************************-*-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
--- /dev/null
+/***********************************************-*-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
--- /dev/null
+/***********************************************-*-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
--- /dev/null
+/***********************************************-*-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
--- /dev/null
+/***********************************************-*-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
--- /dev/null
+/***********************************************-*-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
--- /dev/null
+/***********************************************-*-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
--- /dev/null
+/***********************************************-*-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
--- /dev/null
+/***********************************************-*-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
--- /dev/null
+/***********************************************-*-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
--- /dev/null
+#!/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;
--- /dev/null
+/***********************************************-*-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 }
--- /dev/null
+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) *~
--- /dev/null
+/***********************************************-*-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
--- /dev/null
+/*
+ * 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,
+ ¶llel, 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);
+}
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/***********************************************-*-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