summaryrefslogtreecommitdiff
path: root/rotord/src
diff options
context:
space:
mode:
Diffstat (limited to 'rotord/src')
-rw-r--r--rotord/src/cvimage.cpp30
-rw-r--r--rotord/src/cvimage.h6
-rw-r--r--rotord/src/graph.cpp106
-rw-r--r--rotord/src/nodes_audio_analysis.h17
-rw-r--r--rotord/src/nodes_drawing.h109
-rw-r--r--rotord/src/nodes_filters.h30
-rw-r--r--rotord/src/nodes_maths.h220
-rw-r--r--rotord/src/rendercontext.cpp15
-rwxr-xr-xrotord/src/rotor.cpp121
-rwxr-xr-xrotord/src/rotor.h1314
-rwxr-xr-xrotord/src/rotord.h3
-rwxr-xr-xrotord/src/tinyxml.cpp4
12 files changed, 871 insertions, 1104 deletions
diff --git a/rotord/src/cvimage.cpp b/rotord/src/cvimage.cpp
index a03d81c..361999b 100644
--- a/rotord/src/cvimage.cpp
+++ b/rotord/src/cvimage.cpp
@@ -126,6 +126,15 @@ namespace Rotor {
}
return *this;
}
+ Image & Image::operator=(const Image &other) {
+ //can be optimised? was trying to use other.data.clone()
+ setup(other.w,other.h);
+ //for (int i=0;i<h*w*3;i++) {
+ // rgb.data[i]=other.rgb.data[i];
+ //}
+ memcpy(rgb.data,other.rgb.data,h*w*3); //saves ~2.4 ms copying a 640x360 image
+ return *this;
+ }
//channel rearrangement
//RGBAZ - these are channels 0-4
//HSB - can also have these virtual channels 5-7
@@ -135,7 +144,25 @@ namespace Rotor {
//maybe this could not be the case if the data is owned by this image?
//need to look into auto_ptr
Image & Image::operator*=(const float &amount) {
- rgb*=amount;
+ //cerr<<"amount: "<<amount<<endl;
+ //rgb*=amount;
+ uint8_t amt=(uint8_t)(amount*255.0f);
+ for (int i=0;i<h*w*3;i++) {
+ rgb.data[i]=pixels.multiply[rgb.data[i]][amt];
+ }
+ //again this is faster
+ return *this;
+ }
+ Image & Image::operator+=(const float &amount) {
+ rgb+=(amount*255.0f);
+ return *this;
+ }
+ Image & Image::operator-=(const float &amount) {
+ rgb-=(amount*255.0f);
+ return *this;
+ }
+ Image & Image::operator/=(const float &amount) {
+ rgb/=amount;
return *this;
}
Image * Image::operator*(const float &amount) {
@@ -160,4 +187,5 @@ namespace Rotor {
other->rgb=rgb/amount;
return other;
}
+
}
diff --git a/rotord/src/cvimage.h b/rotord/src/cvimage.h
index 336c41b..feb7298 100644
--- a/rotord/src/cvimage.h
+++ b/rotord/src/cvimage.h
@@ -32,7 +32,7 @@ namespace Rotor {
multiply[i]=new uint8_t[256];
for (int j=0;j<256;j++){
add[i][j]=(uint8_t)std::min(i+j,0xFF);
- multiply[i][j]=(uint8_t)((((float)i)/255.0f)*(((float)j)/255.0f)*255.0f);
+ multiply[i][j]=(uint8_t)(((float)i)*(((float)j)/255.0f));
}
}
mono_weights=new uint8_t*[3];
@@ -166,6 +166,7 @@ namespace Rotor {
*/
return t;
}
+ Image & operator=(const Image &other);
//believe these still work, don't know if these optimisations are better than opencvs..
Image & operator+=(const Image &other);
Image & operator*=(const Image &other);
@@ -176,6 +177,9 @@ namespace Rotor {
Image & add_wrap(const Image &other);
Image & divide_wrap(const Image &other);
Image & operator*=(const float &amount);
+ Image & operator+=(const float &amount);
+ Image & operator-=(const float &amount);
+ Image & operator/=(const float &amount);
Image * operator*(const float &amount);
Image * operator+(const float &amount);
Image * operator-(const float &amount);
diff --git a/rotord/src/graph.cpp b/rotord/src/graph.cpp
index 60af1c8..3aa2925 100644
--- a/rotord/src/graph.cpp
+++ b/rotord/src/graph.cpp
@@ -22,6 +22,7 @@ Node* Graph::find_node(const string &type){
}
return nullptr; //can be tested against
};
+/*
bool Graph::signal_render(string &signal_xml,const float framerate) {
if (find_node("signal_output")) {
Signal_output *signal_output=dynamic_cast<Signal_output*>(find_node("signal_output"));
@@ -31,6 +32,7 @@ bool Graph::signal_render(string &signal_xml,const float framerate) {
return false;
}
+*/
bool Graph::video_render(const string &output_filename,const string &audio_filename,const float framerate,float& progress) {
vector<Node*> loaders=find_nodes("video_loader");
for (auto i:loaders){
@@ -47,6 +49,7 @@ bool Graph::video_render(const string &output_filename,const string &audio_filen
cerr<<"Rotor: video output node not found"<<endl;
return false;
}
+
bool Graph::set_resolution(int w,int h){
if (w>64&&h>48){
outW=w;
@@ -83,61 +86,100 @@ bool Graph::parseXml(string media_path){
settings[attr]=xml.getAttribute("node",attr,"",i1);
//cerr << "Got attribute: " << attr << ":" << xml.getAttribute("node",attr,"",i1) << endl;
}
- settings["description"]=xml.getValue("node","",i1);
settings["media_path"]=media_path;
Node* node=factory.create(settings);
if (node) {
string nodeID=xml.getAttribute("node","ID","",i1);
if (nodes.find(nodeID)==nodes.end()){
+ string nodetype=xml.getAttribute("node","type","",i1);
cerr << "Rotor: creating node '"<<nodeID<<"': '"<< xml.getAttribute("node","type","",i1) << "'" << endl;
nodes[nodeID]=node;
if(xml.pushTag("node",i1)) {
int n2=xml.getNumTags("signal_input");
for (int i2=0;i2<n2;i2++){
- nodes[nodeID]->create_signal_input(xml.getValue("signal_input","",i2));
- string fromID=xml.getAttribute("signal_input","from","",i2);
- if (nodes.find(fromID)!=nodes.end()) {
- if (!nodes[nodeID]->inputs[i2]->connect((Signal_node*)nodes[fromID])){
- cerr << "Rotor: graph loader cannot connect input " << i2 << " of node '" << nodeID << "' to node '" << fromID << "'" << endl;
- return false;
+ //TODO expand
+ //nodes[nodeID]->create_signal_input(xml.getAttribute("signal_input","description","",i2),xml.getAttribute("signal_input","title","",i2));
+ if ((nodes[nodeID])->inputs.size()>i2) {
+ string fromID=xml.getAttribute("signal_input","from","",i2);
+ if (nodes.find(fromID)!=nodes.end()) {
+ if (!nodes[nodeID]->inputs[i2]->connect((Signal_node*)nodes[fromID])){
+ cerr << "Rotor: graph loader cannot connect input " << i2 << " of node '" << nodeID << "' to node '" << fromID << "'" << endl;
+ return false;
+ }
+ else cerr << "Rotor: linked input " << i2 << " of node '" << nodeID << "' to node '" << fromID << "'" << endl;
}
- else cerr << "Rotor: linked input " << i2 << " of node '" << nodeID << "' to node '" << fromID << "'" << endl;
+ else cerr << "Rotor: linking input " << i2 << " of node: '" << nodeID << "', cannot find target '" << fromID << "'" << endl;
}
- else cerr << "Rotor: linking input " << i2 << " of node: '" << nodeID << "', cannot find target '" << fromID << "'" << endl;
+ else cerr << "Rotor: input " << i2 << " of node: '" << nodeID << "' does not exist" << endl;
+
}
int n3=xml.getNumTags("image_input");
for (int i3=0;i3<n3;i3++){
- ((Image_node*)nodes[nodeID])->create_image_input(xml.getValue("image_input","",i3));
- string fromID=xml.getAttribute("image_input","from","",i3);
- if (nodes.find(fromID)!=nodes.end()) {
- if (!(((Image_node*)nodes[nodeID])->image_inputs[i3]->connect((Image_node*)nodes[fromID]))){
- cerr << "Rotor: graph loader cannot connect image input " << i3 << " of node '" << nodeID << "' to node '" << fromID << "'" << endl;
- return false;
+ //handle expandable inputs
+ if (((Image_node*)nodes[nodeID])->image_inputs.size()<=i3&((Image_node*)nodes[nodeID])->duplicate_inputs){
+ string desc=((Image_node*)nodes[nodeID])->image_inputs[0]->description;
+ string title=((Image_node*)nodes[nodeID])->image_inputs[0]->title;
+ while(((Image_node*)nodes[nodeID])->image_inputs.size()<=i3){
+ ((Image_node*)nodes[nodeID])->create_image_input(desc,title);
+ }
+ }
+ //((Image_node*)nodes[nodeID])->create_image_input(xml.getValue("image_input","",i3));
+ if (((Image_node*)nodes[nodeID])->image_inputs.size()>i3) {
+ string fromID=xml.getAttribute("image_input","from","",i3);
+ if (nodes.find(fromID)!=nodes.end()) {
+ if (!(((Image_node*)nodes[nodeID])->image_inputs[i3]->connect((Image_node*)nodes[fromID]))){
+ cerr << "Rotor: graph loader cannot connect image input " << i3 << " of node '" << nodeID << "' to node '" << fromID << "'" << endl;
+ return false;
+ }
+ else cerr << "Rotor: linked image input " << i3 << " of node '" << nodeID << "' to node '" << fromID << "'" << endl;
}
- else cerr << "Rotor: linked image input " << i3 << " of node '" << nodeID << "' to node '" << fromID << "'" << endl;
+ else cerr << "Rotor: linking image input " << i3 << " of node: '" << nodeID << "', cannot find target '" << fromID << "'" << endl;
}
- else cerr << "Rotor: linking image input " << i3 << " of node: '" << nodeID << "', cannot find target '" << fromID << "'" << endl;
+ else cerr << "Rotor: image input number " << i3 << " of node: '" << nodeID << "' does not exist" << endl;
}
- int n4=xml.getNumTags("parameter_input");
+ int n4=xml.getNumTags("parameter");
for (int i4=0;i4<n4;i4++){
- nodes[nodeID]->create_parameter_input(xml.getAttribute("parameter_input","parameter","",i4),xml.getValue("parameter_input","",i4));
- string fromID=xml.getAttribute("parameter_input","from","",i4);
- if (nodes.find(fromID)!=nodes.end()) {
- if (!nodes[nodeID]->parameter_inputs[i4]->connect(nodes[fromID])){
- cerr << "Rotor: graph loader cannot connect parameter input " << i4 << " of node '" << nodeID << "' to node '" << fromID << "'" << endl;
- return false;
+ string parameter=xml.getAttribute("parameter","name","",i4);
+ if (nodes[nodeID]->parameters.find(parameter)!=nodes[nodeID]->parameters.end()) {
+ string val=xml.getAttribute("parameter","value","",i4);
+ if (val!="") nodes[nodeID]->parameters.find(parameter)->second->value=ofToFloat(val);
+ string fromID=xml.getAttribute("parameter","from","",i4);
+ if (nodes.find(fromID)!=nodes.end()) {
+ if (!nodes[nodeID]->parameters[parameter]->connect(nodes[fromID])){
+ cerr << "Rotor: graph loader cannot connect parameter input " << i4 << " of node '" << nodeID << "' to node '" << fromID << "'" << endl;
+ return false;
+ }
+ else cerr << "Rotor: linked parameter input " << i4 << " of node '" << nodeID << "' to node '" << fromID << "'" << endl;
}
- else cerr << "Rotor: linked parameter input " << i4 << " of node '" << nodeID << "' to node '" << fromID << "'" << endl;
+ else cerr << "Rotor: linking parameter input " << i4 << " of node: '" << nodeID << "', cannot find target '" << fromID << "'" << endl;
}
- else cerr << "Rotor: linking parameter input " << i4 << " of node: '" << nodeID << "', cannot find target '" << fromID << "'" << endl;
+ else cerr << "Rotor: cannot find parameter input '" << parameter << "' of "<<nodetype<<" "<< nodeID << endl;
}
- nodes[nodeID]->link_params();
- //extra key/value pairs that can be specific to sub-settings
- int n5=xml.getNumTags("parameter");
- for (int i5=0;i5<n5;i5++){
- nodes[nodeID]->set_parameter(xml.getAttribute("parameter","name","",i5),xml.getAttribute("parameter","value","",i5));
+ //still support old parameter_input
+ n4=xml.getNumTags("parameter_input");
+ for (int i4=0;i4<n4;i4++){
+ string parameter=xml.getAttribute("parameter_input","name","",i4);
+ if (nodes[nodeID]->parameters.find(parameter)!=nodes[nodeID]->parameters.end()) {
+ string val=xml.getAttribute("parameter_input","value","",i4);
+ if (val!="") nodes[nodeID]->parameters.find(parameter)->second->value=ofToFloat(val);
+ string fromID=xml.getAttribute("parameter_input","from","",i4);
+ if (nodes.find(fromID)!=nodes.end()) {
+ if (!nodes[nodeID]->parameters[parameter]->connect(nodes[fromID])){
+ cerr << "Rotor: graph loader cannot connect parameter input " << i4 << " of node '" << nodeID << "' to node '" << fromID << "'" << endl;
+ return false;
+ }
+ else cerr << "Rotor: linked parameter input " << i4 << " of node '" << nodeID << "' to node '" << fromID << "'" << endl;
+ }
+ else cerr << "Rotor: linking parameter input " << i4 << " of node: '" << nodeID << "', cannot find target '" << fromID << "'" << endl;
+ }
+ else cerr << "Rotor: cannot find parameter input '" << parameter << "' of "<<nodetype<<" "<< nodeID << endl;
}
- if (n5>0) cerr << "Rotor: found " << n5 << " extra parameters for node '" << nodeID << "'" << endl;
+ //extra key/value pairs that can be specific to sub-settings - WHAT WAS THIS
+ //int n5=xml.getNumTags("parameter");
+ //for (int i5=0;i5<n5;i5++){
+ // nodes[nodeID]->set_parameter(xml.getAttribute("parameter","name","",i5),xml.getAttribute("parameter","value","",i5));
+ //}
+ //if (n5>0) cerr << "Rotor: found " << n5 << " extra parameters for node '" << nodeID << "'" << endl;
xml.popTag();
}
diff --git a/rotord/src/nodes_audio_analysis.h b/rotord/src/nodes_audio_analysis.h
index e6c1e65..9252db4 100644
--- a/rotord/src/nodes_audio_analysis.h
+++ b/rotord/src/nodes_audio_analysis.h
@@ -5,15 +5,26 @@
#include "vampHost.h"
namespace Rotor {
- class Audio_analysis: public Base_audio_processor {
+ class Audio_analysis: public Audio_processor {
public:
- Audio_analysis(){};
- Audio_analysis(map<string,string> &settings) {
+ Audio_analysis(){
+ //create_attribute("soname","Plugin library to use","Plugin library","vamp-example-plugins",{"horiz","vert","horizR","vertR"});
+ //create_attribute("id","ID of Plugin to use","Plugin ID","percussiononsets",{"horiz","vert","horizR","vertR"});
+ create_attribute("analyser","Analyser Plugin to use","Analyser plugin","barbeattracker",{"barbeattracker","segmenter"});
+ create_parameter("outputNo","number","Plugin output to use","Output number",0.0f);
+ title="Audio analysis";
+ description="Analyse audio and output";
+ };
+ Audio_analysis(map<string,string> &settings):Audio_analysis() {
base_settings(settings);
+ vector< pair< string, string>> sonames={
+ {"qm-vamp-plugins","qm-barbeattracker"}
+ };
soname=find_setting(settings,"soname");
id=find_setting(settings,"id");
outputNo=find_setting(settings,"outputNo",0);
};
+ ~Audio_analysis(){};
Audio_analysis* clone(map<string,string> &_settings) { return new Audio_analysis(_settings);};
bool init(int _channels,int _bits,int _samples,int _rate);
void cleanup();
diff --git a/rotord/src/nodes_drawing.h b/rotord/src/nodes_drawing.h
index 81ef590..c44527d 100644
--- a/rotord/src/nodes_drawing.h
+++ b/rotord/src/nodes_drawing.h
@@ -5,60 +5,50 @@
#include <cairo.h>
namespace Rotor {
- class Draw_node: public Image_node { //base class for drawing with cairo
+ class Draw_node: public Image_node {
public:
- Draw_node(){image=nullptr;};
- Draw_node(map<string,string> &settings) {
- image=nullptr;
+ Draw_node(){
+ create_image_input("image input","Image input");
+ //no title or description as it isn't intended for the user
};
- ~Draw_node(){ if (image) delete image;};
+ Draw_node(map<string,string> &settings):Draw_node() {
+ };
+ ~Draw_node(){};
Draw_node* clone(map<string,string> &_settings) { return new Draw_node(_settings);};
virtual void vector_output(cairo_t * cr,const Frame_spec &frame){};
Image *output(const Frame_spec &frame){
- //get new or input image to draw upon
- if (image_inputs.size()) {
- if (image_inputs[0]->connection){
- //copy incoming image **writable
- if (image) delete image;
- image=(((Image_node*)image_inputs[0]->connection)->get_output(frame))->clone();
- }
- else {
- if (!image) image =new Image();
- image->setup(frame.w,frame.h);
- }
+ Image *in=image_inputs[0]->get(frame);
+ if (in){
+ image=(*in);
}
- else {
- if (!image) image =new Image();
- image->setup(frame.w,frame.h); //do this twice or use a goto
- }
//convert to 32 bit - this can probably be optimised further
cv::Mat chans;
- cv::cvtColor(image->rgb, chans, CV_BGR2RGBA, 4);
+ cv::cvtColor(image.rgb, chans, CV_BGR2RGBA, 4);
cairo_surface_t * cs = cairo_image_surface_create_for_data (chans.data,
CAIRO_FORMAT_RGB24,
- image->w,
- image->h,
- image->w*4);
+ image.w,
+ image.h,
+ image.w*4);
cairo_t * cr = cairo_create (cs);
//do any kind of vector drawing
vector_output(cr,frame);
//convert frame back to 24 bits
- cv::cvtColor(chans,image->rgb,CV_RGBA2BGR,3);
+ cv::cvtColor(chans,image.rgb,CV_RGBA2BGR,3);
cairo_destroy(cr);
cairo_surface_destroy(cs);
- return image;
+ return &image;
}
private:
- Image *image; //is an image generator
};
class Hello_draw: public Draw_node {
public:
- Hello_draw(){image=nullptr;};
+ Hello_draw(){
+ //no title or description as it isn't intended for the user
+ };
Hello_draw(map<string,string> &settings) {
- image=nullptr;
base_settings(settings);
};
- ~Hello_draw(){ if (image) delete image;};
+ ~Hello_draw(){};
Hello_draw* clone(map<string,string> &_settings) { return new Hello_draw(_settings);};
void vector_output(cairo_t * cr,const Frame_spec &frame){
cairo_text_extents_t te;
@@ -72,54 +62,36 @@ namespace Rotor {
cairo_fill(cr);
}
private:
- Image *image; //is an image generator
};
#define SHAPE_circle 1
#define SHAPE_square 2
#define SHAPE_triangle 3
class Shape: public Draw_node {
public:
- Shape(){image=nullptr;};
- Shape(map<string,string> &settings) {
- image=nullptr;
- base_settings(settings);
- scale=find_setting(settings,"scale",1.0f);
- rotation=find_setting(settings,"rotation",0.0f);
- x=find_setting(settings,"x",0.0f);
- y=find_setting(settings,"y",0.0f);
- colour=Colour(find_setting(settings,"colour","FFFFFF"));
- string _shape=find_setting(settings,"shape","square");
- if (_shape=="circle") shape=SHAPE_circle;
- if (_shape=="square") shape=SHAPE_square;
- if (_shape=="triangle") shape=SHAPE_triangle;
+ Shape(){
+ title="Shape";
+ description="Draws filled shapes";
+ create_parameter("x","number","X coordinate","X",0.0f);
+ create_parameter("y","number","Y coordinate","Y",0.0f);
+ create_parameter("scale","number","Scale","Scale",1.0f);
+ create_parameter("rotation","number","Rotation","Rotation",0.0f);
+ create_attribute("colour","Colour to fill","Colour","FFFFFF");
+ create_attribute("shape","Shape to draw","Shape","square",{"circle","square","triangle"});
};
- void link_params() {
- for (auto p:parameter_inputs){
- if (p->parameter=="scale") {
- p->receiver=&scale;
- }
- if (p->parameter=="rotation") {
- p->receiver=&rotation;
- }
- if (p->parameter=="x") {
- p->receiver=&x;
- }
- if (p->parameter=="y") {
- p->receiver=&y;
- }
- }
-
+ Shape(map<string,string> &settings):Shape() {
+ base_settings(settings);
+ colour=Colour(attributes["colour"]->value);
};
- ~Shape(){ if (image) delete image;};
+ ~Shape(){};
Shape* clone(map<string,string> &_settings) { return new Shape(_settings);};
void vector_output(cairo_t * cr,const Frame_spec &frame){
cairo_set_source_rgb(cr, colour.Rfloat(),colour.Gfloat(),colour.Bfloat());
cairo_save(cr); //not really even necessary?
cairo_translate(cr, frame.w/2, frame.h/2);
- cairo_translate(cr, x * frame.w, y * frame.h);
- cairo_scale(cr, scale , scale );
- cairo_rotate(cr,(rotation/180.0f)*M_PI);
- switch(shape) {
+ cairo_translate(cr, parameters["x"]->value * frame.w, parameters["y"]->value * frame.h);
+ cairo_scale(cr, parameters["scale"]->value , parameters["scale"]->value );
+ cairo_rotate(cr,(parameters["rotation"]->value/180.0f)*M_PI);
+ switch(attributes["shape"]->intVal) {
case SHAPE_square:
cairo_rectangle(cr,-frame.w/2,-frame.w/2,frame.w,frame.w);
break;
@@ -138,13 +110,8 @@ namespace Rotor {
cairo_fill(cr);
}
private:
- Image *image; //is an image generator
- int shape;
- float scale;
- float rotation;
- float x,y;
Colour colour;
};
}
-#endif \ No newline at end of file
+#endif
diff --git a/rotord/src/nodes_filters.h b/rotord/src/nodes_filters.h
index c6de606..263b4b2 100644
--- a/rotord/src/nodes_filters.h
+++ b/rotord/src/nodes_filters.h
@@ -6,35 +6,29 @@
namespace Rotor {
class Blur: public Image_node {
public:
- Blur(){};
+ Blur(){
+ title="Blur";
+ description="Gaussian blur filter";
+ create_parameter("size","number","Blur window size","Size",1.0f);
+ create_image_input("image input","Image input");
+ };
Blur(map<string,string> &settings) {
base_settings(settings);
- size=find_setting(settings,"size",0.0f);
};
~Blur(){
};
- void link_params() {
- for (auto p:parameter_inputs){
- p->receiver=nullptr;
- if (p->parameter=="size") p->receiver=&size;
- }
- };
Blur* clone(map<string,string> &_settings) { return new Blur(_settings);};
Image *output(const Frame_spec &frame){
- if (image_inputs.size()) {
- if (image_inputs[0]->connection){
- Image *other=(((Image_node*)image_inputs[0]->connection)->get_output(frame));
- image.setup(other->w,other->h);
- int ksize=max((ceil(size/2.0)*2)+1,1.0);
- //nb this doesn't do the intended: create 'continuously variable' blur
- cv::GaussianBlur((*other).rgb,image.rgb,cvSize(ksize,ksize),size);
- return &image;
- }
+ Image *in=image_inputs[0]->get(frame);
+ if (in) {
+ int ksize=max((ceil(parameters["size"]->value/2.0)*2)+1,1.0);
+ //nb this doesn't do the intended: create 'continuously variable' blur
+ cv::GaussianBlur((*in).rgb,image.rgb,cvSize(ksize,ksize),parameters["size"]->value);
+ return &image;
}
return nullptr;
}
private:
- Image image;
float size;
};
}
diff --git a/rotord/src/nodes_maths.h b/rotord/src/nodes_maths.h
index d4b8eac..4ec2cf9 100644
--- a/rotord/src/nodes_maths.h
+++ b/rotord/src/nodes_maths.h
@@ -4,6 +4,7 @@
#include "rotor.h"
#include <libnoise/noise.h>
#include <libnoise/mathconsts.h>
+#include "Poco/Logger.h"
namespace Rotor {
#define COMPARISON_Equal 1
@@ -14,54 +15,40 @@ namespace Rotor {
#define COMPARISON_Less_or_equal 6
class Comparison: public Signal_node {
public:
- Comparison(){};
- Comparison(map<string,string> &settings) {
+ Comparison(){
+ create_signal_input("signal","Signal");
+ create_parameter("value","number","Value or signal for operation","Value",0.0f);
+ create_attribute("operator","Operator for comparison","operator","==",{"==","!=",">","<",">=","<="});
+ description="Compares the signal with a value or signal according to the operator";
+ };
+ Comparison(map<string,string> &settings):Comparison() {
base_settings(settings);
- value=find_setting(settings,"value",0.0f);
- string _op=find_setting(settings,"operator","==");
- if (_op=="==") op=COMPARISON_Equal;
- if (_op=="!=") op=COMPARISON_Not_equal;
- if (_op==">") op=COMPARISON_Greater;
- if (_op=="<") op=COMPARISON_Less;
- if (_op==">=") op=COMPARISON_Greater_or_equal;
- if (_op=="<=") op=COMPARISON_Less_or_equal;
}
- void link_params() {
- for (auto p:parameter_inputs){
- if (p->parameter=="value") p->receiver=&value;
- }
- };
Comparison* clone(map<string,string> &_settings) { return new Comparison(_settings);};
- const float output(const Time_spec &time) {
- if (inputs.size()) { //there should there be a way to specify number of inputs in the code rather than in xml
- if (inputs[0]->connection) {
- float in= (((Signal_node*)inputs[0]->connection)->get_output(time));
- switch (op) {
- case COMPARISON_Equal:
- return fequal(value,in)?1.0f:0.0f;
- break;
- case COMPARISON_Not_equal:
- return fequal(value,in)?0.0f:1.0f;
- break;
- case COMPARISON_Greater:
- return fgreater(value,in)?1.0f:0.0f;
- break;
- case COMPARISON_Less:
- return fless(value,in)?1.0f:0.0f;
- break;
- case COMPARISON_Greater_or_equal:
- return fgreater_or_equal(value,in)?1.0f:0.0f;
- break;
- case COMPARISON_Less_or_equal:
- return fless_or_equal(value,in)?1.0f:0.0f;
- break;
- }
- }
- }
+ const float output(const Time_spec &time) {
+ switch (attributes["operator"]->intVal) {
+ case COMPARISON_Equal:
+ return fequal(parameters["value"]->value,inputs[0]->get(time))?1.0f:0.0f;
+ break;
+ case COMPARISON_Not_equal:
+ return fequal(parameters["value"]->value,inputs[0]->get(time))?0.0f:1.0f;
+ break;
+ case COMPARISON_Greater:
+ return fgreater(parameters["value"]->value,inputs[0]->get(time))?1.0f:0.0f;
+ break;
+ case COMPARISON_Less:
+ return fless(parameters["value"]->value,inputs[0]->get(time))?1.0f:0.0f;
+ break;
+ case COMPARISON_Greater_or_equal:
+ return fgreater_or_equal(parameters["value"]->value,inputs[0]->get(time))?1.0f:0.0f;
+ break;
+ case COMPARISON_Less_or_equal:
+ return fless_or_equal(parameters["value"]->value,inputs[0]->get(time))?1.0f:0.0f;
+ break;
+ }
+
return 0.0f;
}
- int op;
- float value;
};
#define ARITHMETIC_plus 1
#define ARITHMETIC_minus 2
@@ -75,70 +62,58 @@ namespace Rotor {
#define ARITHMETIC_jolt 10
class Arithmetic: public Signal_node {
public:
- Arithmetic(){};
- Arithmetic(map<string,string> &settings) {
+ Arithmetic(){
+ create_signal_input("signal","Signal");
+ create_parameter("value","number","Value or signal for operation","Value",1.0f);
+ create_attribute("operator","operator for image","Operator","+",{"+","-","*","/","%","^","sin","cos","ease","jolt"});
+ title="Arithmetic";
+ description="Performs arithmetic on a signal with a signal or value";
+ };
+ Arithmetic(map<string,string> &settings):Arithmetic() {
base_settings(settings);
- value=find_setting(settings,"value",0.0f);
- string _op=find_setting(settings,"operator","+");
- if (_op=="+"||_op=="plus"||_op=="add") op=ARITHMETIC_plus;
- if (_op=="-"||_op=="minus"||_op=="subtract") op=ARITHMETIC_minus;
- if (_op=="*"||_op=="x"||_op=="multiply") op=ARITHMETIC_multiply;
- if (_op=="/"||_op=="divide") op=ARITHMETIC_divide;
- if (_op=="%"||_op=="mod"||_op=="modulo"||_op=="modulus") op=ARITHMETIC_modulo;
- if (_op=="^"||_op=="power") op=ARITHMETIC_pow;
- if (_op=="sin"||_op=="sine") op=ARITHMETIC_sin;
- if (_op=="cos"||_op=="cos") op=ARITHMETIC_cos;
- if (_op=="ease") op=ARITHMETIC_ease;
- if (_op=="jolt") op=ARITHMETIC_jolt;
- }
- void link_params() {
- for (auto p:parameter_inputs){
- p->receiver=nullptr;
- if (p->parameter=="value") p->receiver=&value;
- }
};
Arithmetic* clone(map<string,string> &_settings) { return new Arithmetic(_settings);};
const float output(const Time_spec &time) {
- if (op==ARITHMETIC_divide||op==ARITHMETIC_modulo){
+ if (attributes["operator"]->intVal==ARITHMETIC_divide||attributes["operator"]->intVal==ARITHMETIC_modulo){
if (value==0.0f) {
- Logger& logger = Logger::get("Rotor");
+ Poco::Logger& logger = Poco::Logger::get("Rotor");
logger.error("Arithmetic node: caught division by zero, frame "+time.frame());
return 0.0f;
}
}
if (inputs.size()) { //there should there be a way to specify number of inputs in the code rather than in xml
if (inputs[0]->connection) {
- float in= (((Signal_node*)inputs[0]->connection)->get_output(time));
- switch (op) {
+ float in= inputs[0]->get(time);
+ switch (attributes["operator"]->intVal) {
case ARITHMETIC_plus:
- return in+value;
+ return in+parameters["value"]->value;
break;
case ARITHMETIC_minus:
- return in-value;
+ return in-parameters["value"]->value;
break;
case ARITHMETIC_multiply:
- return in*value;
+ return in*parameters["value"]->value;
break;
case ARITHMETIC_divide:
- return in/value;
+ return in/parameters["value"]->value;
break;
case ARITHMETIC_modulo:
- return fmod(in,value);
+ return fmod(in,parameters["value"]->value);
break;
case ARITHMETIC_pow:
- return pow(in,value);
+ return pow(in,parameters["value"]->value);
break;
case ARITHMETIC_sin:
- return sin(in)*value;
+ return sin(in)*parameters["value"]->value;
break;
case ARITHMETIC_cos:
- return cos(in)*value;
+ return cos(in)*parameters["value"]->value;
break;
case ARITHMETIC_ease:
- return ((1.0-value)*in)+(value*(0.5f+((cos((fmod(in,1.0f)+1.0f)*M_PI))*0.5f)));
+ return ((1.0-parameters["value"]->value)*in)+(parameters["value"]->value*(0.5f+((cos((fmod(in,1.0f)+1.0f)*M_PI))*0.5f)));
break;
case ARITHMETIC_jolt:
- return ((1.0-value)*in)+(value*(0.5f+((sin((fmod(in,1.0f)+1.0f)*M_PI))*0.5f)));
+ return ((1.0-parameters["value"]->value)*in)+(parameters["value"]->value*(0.5f+((sin((fmod(in,1.0f)+1.0f)*M_PI))*0.5f)));
break;
}
}
@@ -148,49 +123,32 @@ namespace Rotor {
int op;
float value;
};
- class Signal_divide: public Signal_node {
- public:
- Signal_divide(){};
- Signal_divide(map<string,string> &settings) {
- base_settings(settings);
- divide_amount=ofToFloat(find_setting(settings,"amount"));
- for (auto p:parameter_inputs){
- if (p->parameter=="amount") p->receiver=&divide_amount;
- }
- };
- Signal_divide* clone(map<string,string> &_settings) { return new Signal_divide(_settings);};
- const float output(const Time_spec &time) {
- if (inputs.size()) { //there should there be a way to specify number of inputs in the code rather than in xml
- if (inputs[0]->connection) {
- return (((Signal_node*)inputs[0]->connection)->get_output(time))/divide_amount;
- }
- }
- return 0.0f;
- }
- float divide_amount;
- };
class Is_new_integer: public Signal_node {
public:
- Is_new_integer(){};
- Is_new_integer(map<string,string> &settings) {
+ Is_new_integer(){
+ title="New integer";
+ description="Outputs 1 on the frame that a signal becomes a new integer";
+ create_signal_input("signal","Signal");
+ };
+ Is_new_integer(map<string,string> &settings):Is_new_integer() {
base_settings(settings);
};
Is_new_integer* clone(map<string,string> &_settings) { return new Is_new_integer(_settings);};
const float output(const Time_spec &time) {
- if (inputs[0]->connection) {
- float s1=(((Signal_node*)(inputs[0]->connection))->get_output(time));
- float s2=(((Signal_node*)(inputs[0]->connection))->get_output(time.lastframe()));
- if (((int)s1)>((int)s2)) {
- return 1.0f;
- }
+ if (((int)inputs[0]->get(time))>((int)inputs[0]->get(time.lastframe()))) {
+ return 1.0f;
}
return 0.0f;
}
};
class On_off: public Signal_node {
public:
- On_off(){};
- On_off(map<string,string> &settings) {
+ On_off(){
+ title="On off";
+ description="Outputs 1 if the integer floor of the signal is even";
+ create_signal_input("signal","Signal");
+ };
+ On_off(map<string,string> &settings):On_off() {
base_settings(settings);
};
On_off* clone(map<string,string> &_settings) { return new On_off(_settings);};
@@ -228,47 +186,45 @@ namespace Rotor {
return fnv1a(*ptr , hash);
}
class Random: public Signal_node {
- //randomises integer while keeping fraction
public:
- Random(){};
+ Random(){
+ title="Random";
+ description="Randomises integer part of signal (seedable)";
+ create_signal_input("signal","Signal");
+ create_parameter("seed","number","Seed value","Seed",1.0f);
+ };
Random(map<string,string> &settings) {
base_settings(settings);
- seed=(Seed+find_setting(settings,"seed",0));
};
Random* clone(map<string,string> &_settings) { return new Random(_settings);};
const float output(const Time_spec &time) {
- if (inputs.size()) {
- if (inputs[0]->connection) {
-
- //hash the integer part and add the fractional part back on
- float o=(((Signal_node*)inputs[0]->connection)->get_output(time));
- uint32_t m=(int)o;
- return ((float)(fnv1a(m,seed)%((uint32_t)time.duration)))+(o-m);
- }
- }
- return 0.0f;
+ uint32_t seed=Seed+parameters["seed"]->value;
+ //hash the integer part and add the fractional part back on
+ float o=inputs[0]->get(time);
+ uint32_t m=(int)o;
+ return ((float)(fnv1a(m,seed)%((uint32_t)time.duration)))+(o-m);
}
- uint32_t seed;
- private:
};
class Noise: public Signal_node {
//fractal noise
public:
- Noise(){};
+ Noise(){
+ title="Noise";
+ description="Fractal noise (seedable)";
+ create_signal_input("signal","Signal");
+ create_parameter("seed","number","Seed value","Seed",1.0f);
+ };
Noise(map<string,string> &settings) {
base_settings(settings);
seed=find_setting(settings,"seed",0);
};
Noise* clone(map<string,string> &_settings) { return new Noise(_settings);};
const float output(const Time_spec &time) {
- if (inputs.size()) {
- if (inputs[0]->connection) {
- float o=(((Signal_node*)inputs[0]->connection)->get_output(time));
-
- return o;
- }
- }
- return 0.0f;
+ uint32_t seed=Seed+parameters["seed"]->value;
+ //hash the integer part and add the fractional part back on
+ float o=inputs[0]->get(time);
+ uint32_t m=(int)o;
+ return ((float)(fnv1a(m,seed)%((uint32_t)time.duration)))+(o-m);
}
uint32_t seed;
private:
diff --git a/rotord/src/rendercontext.cpp b/rotord/src/rendercontext.cpp
index 6ef0420..e582dce 100644
--- a/rotord/src/rendercontext.cpp
+++ b/rotord/src/rendercontext.cpp
@@ -1,7 +1,9 @@
-#include "rotor.h"
-
+#include "rendercontext.h"
using namespace Rotor;
+using Poco::Net::HTTPResponse;
+using Poco::Logger;
+
void Render_context::runTask() {
while (!isCancelled()) {
int cmd=0;
@@ -13,11 +15,11 @@ void Render_context::runTask() {
mutex.unlock();
if(cmd==ANALYSE_AUDIO) {
state=ANALYSING_AUDIO;
- vector<Base_audio_processor*> processors;
+ vector<Audio_processor*> processors;
processors.push_back(audio_thumb);
vector<Node*> analysers=graph.find_nodes("audio_analysis");
for (auto a: analysers) {
- processors.push_back(dynamic_cast<Base_audio_processor*>(a));
+ processors.push_back(dynamic_cast<Audio_processor*>(a));
}
if (load_audio(audio_filename,processors)) {
audio_loaded=true;
@@ -218,7 +220,7 @@ void Render_context::session_command(const std::vector<std::string>& command,xml
// framerate=ofToFloat(command[3]);
//}
string signal_xml;
- if (graph.signal_render(signal_xml,framerate)){
+ if (false) { //graph.signal_render(signal_xml,framerate)){
status=HTTPResponse::HTTP_OK;
logger.information("rendering signal to xml");
XML.addValue("signal",signal_xml); //this doesn't work >> pseudo xml
@@ -288,7 +290,6 @@ void Render_context::session_command(const std::vector<std::string>& command,xml
XML.addValue("progress",ofToString(progress));
}
else {
- status=HTTPResponse::HTTP_BAD_REQUEST;
logger.error("ERROR: Render progress requested but not rendering");
XML.addValue("error","Not rendering");
}
@@ -325,7 +326,7 @@ void Render_context::session_command(const std::vector<std::string>& command,xml
}
}
-bool Render_context::load_audio(const string &filename,vector<Base_audio_processor*> processors){
+bool Render_context::load_audio(const string &filename,vector<Audio_processor*> processors){
Logger& logger = Logger::get("Rotor");
logger.information("Starting audio analysis");
diff --git a/rotord/src/rotor.cpp b/rotord/src/rotor.cpp
index c7e7bc7..99fe396 100755
--- a/rotord/src/rotor.cpp
+++ b/rotord/src/rotor.cpp
@@ -1,87 +1,74 @@
#include "rotor.h"
#include "nodes_audio_analysis.h"
-#include "nodes_drawing.h"
#include "nodes_maths.h"
+#include "nodes_drawing.h"
#include "nodes_filters.h"
using namespace Rotor;
+using Poco::Logger;
Node_factory::Node_factory(){
//for now, statically load prototype map in constructor
- add_type("audio_analysis",new Audio_analysis());
- add_type("divide",new Signal_divide());
- add_type("bang",new Is_new_integer());
+ add_type("time",new Time());
+ add_type("track_time",new Track_time());
add_type("signal_output",new Signal_output());
add_type("testcard",new Testcard());
- add_type("video_output",new Video_output());
- add_type("video_loader",new Video_loader());
- add_type("on_off",new On_off());
add_type("invert",new Invert());
add_type("video_cycler",new Video_cycler());
- add_type("luma_levels",new Luma_levels());
- add_type("echo_trails",new Echo_trails());
- add_type("time",new Time());
- add_type("track_time",new Track_time());
- add_type("comparison",new Comparison()); //TODO: alias to symbols
- add_type("arithmetic",new Arithmetic()); //TODO: alias to symbols
add_type("signal_colour",new Signal_colour());
add_type("signal_greyscale",new Signal_greyscale());
add_type("image_arithmetic",new Image_arithmetic());
- add_type("random",new Random());
+ add_type("luma_levels",new Luma_levels());
+ add_type("echo_trails",new Echo_trails());
add_type("blend",new Blend());
add_type("mirror",new Mirror());
add_type("monochrome",new Monochrome());
add_type("transform",new Transform());
add_type("alpha_merge",new Alpha_merge());
+ //nodes_audio_analysis.h
+ add_type("audio_analysis",new Audio_analysis());
+ //nodes_maths.h
+ add_type("comparison",new Comparison()); //TODO: alias to symbols
+ add_type("arithmetic",new Arithmetic()); //TODO: alias to symbols
+ add_type("bang",new Is_new_integer());
+ add_type("on_off",new On_off());
+ add_type("random",new Random());
+ //nodes_drawing.h
add_type("shape",new Shape());
+ add_type("hello",new Hello_draw());
+ //nodes_filters.h
add_type("blur",new Blur());
+ //video nodes
+ add_type("video_loader",new Video_loader());
+ add_type("video_output",new Video_output());
}
-
-bool Signal_input::connect(Signal_node* source) {
- if (source->output_type=="signal") {
- connection=(Node*)source;
- return true;
- }
+bool Signal_input::connect(Node* source) {
+ connection=dynamic_cast<Signal_node*>(source);
+ if (connection) return true;
else return false;
}
-void Parameter_input::update(const Time_spec& time){ //gets input and updates variable
- if (receiver){
- *receiver=((Signal_node*)connection)->get_output(time);
+float Signal_input::get(const Time_spec& time){ //gets input and updates variable
+ if (connection){
+ return (((Signal_node*)connection)->get_output(time));
}
+ else return 0.0f;
}
-bool Image_input::connect(Image_node* source) {
- if (source->output_type=="image") {
- connection=(Node*)source;
- return true;
- }
+bool Image_input::connect(Node* source) {
+ connection=dynamic_cast<Image_node*>(source);
+ if (connection) return true;
else return false;
}
-void Node::update_params(const Time_spec& time){ //compute connected parameters
- for (auto p:parameter_inputs){
- p->update(time);
+Image* Image_input::get(const Frame_spec& time){ //gets input and updates variable
+ if (connection){
+ return (((Image_node*)connection)->get_output(time));
}
+ else return nullptr;
}
-bool Signal_output::render(const float duration, const float framerate,string &xml_out){
- //testing signal routes
- Logger& logger = Logger::get("Rotor");
- logger.information("SIgnal_output rendering "+ofToString(duration)+" seconds at "+ofToString(framerate)+" fps");
-
- float step=1.0f/framerate;
- float v=0.0f;
- float min=10000000.0f;
- float max=-10000000.0f;
- for (float f=0.0f;f<duration;f+=step) {
- float u=get_output(Time_spec(f,framerate,duration));
- if (!fequal(u,v)) {
- xml_out+=("<signal time='"+ofToString(f)+"'>"+ofToString(u)+"</signal>\n");
- v=u;
- if (v>max) max=v;
- if (v<min) min=v;
- }
+float Parameter::get(const Time_spec& time){ //gets input and updates variable
+ if (connection){
+ value = ((Signal_node*)connection)->get_output(time);
}
- xml_out+=("<signal_finished min='"+ofToString(min)+"' max='"+ofToString(max)+"'/>\n");
- return true;
+ return value;
}
-
bool Audio_thumbnailer::init(int _channels,int _bits,int _samples,int _rate) {
//base_audio_processor::init(_channels,_bits,_samples);
channels=_channels;
@@ -162,6 +149,7 @@ string Audio_thumbnailer::print(){
delete enc;
return output.str();
}
+
bool Audio_analysis::init(int _channels,int _bits,int _samples, int _rate) {
//need these to make sense of data
channels=_channels;
@@ -190,7 +178,6 @@ void Audio_analysis::print_features(){
}
cerr<<endl;
}
-
bool Video_output::render(const float duration, const float framerate,const string &output_filename,const string &audio_filename,float& progress,int outW,int outH){
//
@@ -282,11 +269,11 @@ bool Video_loader::load(const string &_filename){
string uri="file://"+_filename;
isLoaded=player.open(uri);
if (isLoaded){
- logger.information("Video_loader loaded "+filename+": "+ofToString(player.getNumberOfFrames())+" frames, "+ofToString(player.getFrameRate())+" fps, "+ofToString(player.getWidth())+"x"+ofToString(player.getHeight()));
+ logger.information("Video_loader loaded "+_filename+": "+ofToString(player.getNumberOfFrames())+" frames, "+ofToString(player.getFrameRate())+" fps, "+ofToString(player.getWidth())+"x"+ofToString(player.getHeight()));
return true;
}
- logger.error("Video_loader failed to load "+filename);
+ logger.error("Video_loader failed to load "+_filename);
return false;
}
@@ -297,15 +284,15 @@ Image* Video_loader::output(const Frame_spec &frame){
//need to cache frames so as to avoid asking for a frame other than the next one.
//need an algorithm to find the previous keyframe and seek forward
- float clipframerate=(framerate==0.0f?player.getFrameRate():framerate);
+ float clipframerate=(parameters["framerate"]->value==0.0f?player.getFrameRate():parameters["framerate"]->value);
- float clipspeed=(clipframerate/frame.framerate)*speed;
+ float clipspeed=(clipframerate/frame.framerate)*parameters["speed"]->value;
int wanted=(((int) ((frame.time*frame.framerate*clipspeed)+0.5))%(player.getNumberOfFrames()-1))+1; //+1 is necessary because 1st frame in a video is number 1?
if (wanted!=lastframe){
if (!player.fetchFrame(frame.w,frame.h,wanted)) { //seek fail
Logger& logger = Logger::get("Rotor");
- logger.error("Video_loader failed to seek frame "+ofToString(wanted)+" of "+filename);
+ logger.error("Video_loader failed to seek frame "+ofToString(wanted)+" of "+attributes["filename"]->value);
if (image.w>0) return &image; //just return the previous frame if possible
else return nullptr;
@@ -316,22 +303,4 @@ Image* Video_loader::output(const Frame_spec &frame){
return &image;
}
return nullptr;
-};
-/*
-bool CVideo_loader::load(const string &filename){
-
- Poco::Path path;
- string uri="file://"+filename;
- isLoaded=player.open(filename);
- if (isLoaded){
- cerr<<"Rotor::Video_loader: "<<filename<<", "<<player.get(CV_CAP_PROP_FRAME_COUNT)<<" frames "<<", "<<player.get(CV_CAP_PROP_FRAME_WIDTH)<<"x"<<player.get(CV_CAP_PROP_FRAME_HEIGHT)<<endl;
- return true;
- }
- cerr<<"Rotor::Video_loader: failed to load "<<filename<<endl;
- return false;
-}
-Image* CVideo_loader::output(const Frame_spec &frame){
-
- return nullptr;
-};
-*/ \ No newline at end of file
+}; \ No newline at end of file
diff --git a/rotord/src/rotor.h b/rotord/src/rotor.h
index 2af582a..937a884 100755
--- a/rotord/src/rotor.h
+++ b/rotord/src/rotor.h
@@ -1,26 +1,7 @@
#ifndef ROTOR_H
#define ROTOR_H
-/*
-nodes can have many inputs but only 1 output
-
-image nodes that use an image as input can pass on the incoming image only if its unchanged.
-
-TODO - parameter class that automatically links variable to correctly named inputs
-TODO - use try.. catch and dynamic_cast to verify node connections rather than checking 'type' tag
-
-TODO - put the boilerplate code for checking inputs into the base class, finally call checked_output
-
-http://stackoverflow.com/questions/5261658/how-to-seek-in-ffmpeg-c-c
-
-300713
-trying to use opencv video loader for seeking
-not so good - conflicts with included libav - seems to be incorrectly loaded
-
-would maybe need to do something like
-http://stackoverflow.com/questions/12427928/configure-and-build-opencv-to-custom-ffmpeg-install
-
-*/
+//definitions of base classes and types for rendering graph
#include <unordered_map>
#include <deque>
@@ -29,59 +10,13 @@ http://stackoverflow.com/questions/12427928/configure-and-build-opencv-to-custom
#include <sys/time.h>
#include <iostream>
-#include "Poco/Net/HTTPServer.h"
#include "Poco/Net/HTTPResponse.h"
-#include "Poco/UUID.h"
-#include "Poco/UUIDGenerator.h"
-#include "Poco/Notification.h"
-#include "Poco/NotificationCenter.h"
-#include "Poco/Observer.h"
-#include "Poco/ThreadPool.h"
-#include "Poco/Thread.h"
-#include "Poco/Task.h"
-#include "Poco/Runnable.h"
-#include "Poco/Mutex.h"
-#include "Poco/Random.h"
-#include "Poco/AutoPtr.h"
-#include "Poco/File.h"
-#include "Poco/Base64Encoder.h"
-#include "Poco/Path.h"
-#include "Poco/StringTokenizer.h"
#include "Poco/Logger.h"
-
-
-using Poco::UUID;
-using Poco::UUIDGenerator;
-using Poco::Net::HTTPResponse;
-using Poco::Logger;
-
-/*
-extern "C" {
- #include <libavcodec/avcodec.h>
- #include <libavformat/avformat.h>
- #include <libavutil/opt.h>
- #include <libavutil/channel_layout.h>
- #include <libavutil/common.h>
- #include <libavutil/imgutils.h>
- #include <libavutil/mathematics.h>
- #include <libavutil/samplefmt.h>
-
- #include <libavutil/dict.h>
- //#include <libavutil/dict.c> stops the compiler error but causes a linker error. does libavcodec need to be statically linked?
- #include <libavutil/imgutils.h>
- #include <libavutil/samplefmt.h>
- //#include <libavutil/timestamp.h>
-}
-*/
-
-
-#define AUDIO_INBUF_SIZE 20480
-#define AUDIO_REFILL_THRESH 4096
-
-#include <highgui.h> //for opencv video IO
+#include "Poco/Path.h"
+#include "Poco/Base64Encoder.h"
#include "xmlIO.h"
-#include "utils.h" //fequal
+#include "utils.h"
#include "cvimage.h"
#include "libavwrapper.h"
@@ -97,46 +32,17 @@ namespace Rotor {
#define ANALYSE_AUDIO 1
#define PREVIEW 2
#define RENDER 3
-
- //forward declaration
+ //forward declarations
class Node;
class Signal_node;
class Image_node;
- class Parameter_input;
-
- //http://blog.tomaka17.com/2012/03/libavcodeclibavformat-tutorial/
- /* struct Packet {
- explicit Packet(AVFormatContext* ctxt = nullptr) {
- av_init_packet(&packet);
- packet.data = nullptr;
- packet.size=0;
- if (ctxt) reset(ctxt);
- }
-
- Packet(Packet&& other) : packet(std::move(other.packet)) {
- other.packet.data = nullptr;
- }
+ class Parameter;
- ~Packet() {
- if (packet.data)
- av_free_packet(&packet);
- }
-
- void reset(AVFormatContext* ctxt) {
- if (packet.data)
- av_free_packet(&packet);
- if (av_read_frame(ctxt, &packet) < 0)
- packet.data = nullptr;
- }
-
- AVPacket packet;
- };
- */
class Time_spec{
public:
Time_spec(){};
Time_spec(float _time,float _framerate,float _duration){ time=_time; framerate=_framerate; duration=_duration;};
- float time;
+ float time; //num/denom ?
float framerate;
float duration;
Time_spec lastframe() const{
@@ -148,16 +54,14 @@ namespace Rotor {
};
class Frame_spec: public Time_spec{
public:
- Frame_spec(float _time,float _framerate,float _duration,int _w,int _h){ time=_time; framerate=_framerate; duration=_duration; w=_w; h=_h;};
- Frame_spec(int _frame,float _framerate,float _duration,int _w,int _h){ time=((float)_frame)/_framerate; framerate=_framerate; duration=_duration; w=_w; h=_h;};
- //Frame_spec(time,_framerate,_duration,_w,_h);};
-
- //float time; //this hould probably be implemented with a num/denom scheme eventually for accuracy
- //float framerate;
+ Frame_spec(float _time,float _framerate,float _duration,int _w,int _h)
+ { time=_time; framerate=_framerate; duration=_duration; w=_w; h=_h;};
+ Frame_spec(int _frame,float _framerate,float _duration,int _w,int _h)
+ { time=((float)_frame)/_framerate; framerate=_framerate; duration=_duration; w=_w; h=_h;};
int h,w;
- //Frame_spec lastframe(){
- // return Frame_spec(time-(1.0f/framerate),framerate,w,h);
- //}
+ Frame_spec lastframe(){
+ return Frame_spec(time-(1.0f/framerate),framerate,duration,w,h);
+ }
};
class Colour{
public:
@@ -185,17 +89,6 @@ namespace Rotor {
}
uint8_t r,g,b;
};
-
- class Render_status{
- public:
- int id;
- float progress;
- };
- class Render_requirements{
- public:
- int num_performances;
- int num_clips;
- };
class Command_response{
public:
Command_response() { status=Poco::Net::HTTPResponse::HTTP_OK; }
@@ -204,75 +97,131 @@ namespace Rotor {
};
class Input{
public:
- Input(const string &_desc): connection(nullptr),description(_desc){};
+ Input(const string &_desc,const string &_title): connection(nullptr),description(_desc),title(_title){};
Node* connection;
string description;
+ string title;
};
class Image_input: public Input{
public:
- bool connect(Image_node *source);
- Image_input(const string &_desc): Input(_desc){};
+ virtual ~Image_input(){};
+ bool connect(Node *source);
+ Image_input(const string &_desc,const string &_title,Node* _connect): Input(_desc,_title){
+ connect(_connect);
+ };
+ Image* get(const Frame_spec& time);
};
class Signal_input: public Input{
public:
- bool connect(Signal_node *source);
- Signal_input(const string &_desc): Input(_desc){};
+ virtual ~Signal_input(){};
+ bool connect(Node *source);
+ Signal_input(const string &_desc,const string &_title,Node* _connect): Input(_desc,_title){
+ connect(_connect);
+ };
+ float get(const Time_spec& time);
};
- class Parameter_input: public Signal_input{
+ class Parameter: public Signal_input{
public:
- Parameter_input(const string &_param,const string &_desc): Signal_input(_desc),receiver(nullptr),parameter(_param){};
- float *receiver;
- void update(const Time_spec& time);
- string parameter;
+ virtual ~Parameter(){};
+ Parameter(const string &_type,const string &_desc,const string &_title,float _value,float _min,float _max,Node* _connect): Signal_input(_desc,_title,_connect),value(_value),min(_min),max(_max),type(_type){};
+ float value,min,max;
+ float get(const Time_spec& time);
+ string type;
+ };
+ class Attribute{ //description of a static attribute which can be an enumerated string array
+ public:
+ virtual ~Attribute(){};
+ Attribute(const string &_desc,const string &_title,const string &_value,std::vector<std::string> _vals={}): description(_desc),title(_title),value(_value),intVal(0){
+ vals=_vals;
+ };
+ void init(const string &_key){ //inits int value from set::string vals index
+ value=_key;
+ std::vector<std::string>::iterator it=it = find(vals.begin(),vals.end(),value);
+ if (it!=vals.end()){
+ intVal = std::distance(vals.begin(),it)+1; //using 1-index for enums
+ }
+ else intVal=0;
+ }
+ string value,description,title;
+ std::vector<std::string> vals;
+ int intVal;
};
class Node{
public:
- virtual Node* clone(map<string,string> &_settings)=0;
- virtual ~Node(){};
- UUID uid; //every usable node has a UUID
- int id;
- vector<Signal_input*> inputs; //simple node can have signal inputs, output depends on node type
- vector<Parameter_input*> parameter_inputs; //linked parameters can convert from settings to inputs
- void create_signal_input(const string &description) {inputs.push_back(new Signal_input(description));};
- void create_parameter_input(const string &parameter,const string &description) {parameter_inputs.push_back(new Parameter_input(parameter,description));};
+ virtual Node* clone(map<string,string> &_settings)=0; //pure virtual
+ virtual ~Node(){
+ duplicate_inputs=false;
+ };
+ vector<Signal_input*> inputs; //simple node can have signal inputs, output depends on node type
+ unordered_map<string,Parameter*> parameters; //linked parameters can convert from settings to inputs
+ unordered_map<string,Attribute*> attributes;
+ void create_signal_input(const string &_desc,const string &_title,Node* _connect=nullptr ) {
+ inputs.push_back(new Signal_input(_desc,_title,_connect));
+ };
+ void create_parameter(const string &_name,const string &_type,const string &_desc,const string &_title,float _value=1.0f,float _min=0.0f,float _max=0.0f,Node* _connect=nullptr) {
+ parameters[_name]=new Parameter(_type,_desc,_title,_value,_min,_max,_connect);
+ };
+ void create_attribute(const string &_attr,const string &_desc,const string &_title,const string &_value,std::vector<std::string> _vals={}) {
+ attributes[_attr]=new Attribute(_desc,_title,_value,_vals);
+ };
string description;
string type;
- string output_type;
string ID;
+ string title;
+ bool duplicate_inputs;
string find_setting(map<string,string> &settings,string key,string def=""){ if (settings.find(key)!=settings.end()) return settings[key]; else return def;};
float find_setting(map<string,string> &settings,string key,float def){ if (settings.find(key)!=settings.end()) return ofToFloat(settings[key]); else return def;};
int find_setting(map<string,string> &settings,string key,int def){ if (settings.find(key)!=settings.end()) return ofToInt(settings[key]); else return def;};
void base_settings(map<string,string> &settings) {
description=find_setting(settings,"description");
type=find_setting(settings,"type");
- output_type=find_setting(settings,"output");
ID=find_setting(settings,"ID");
+ title=find_setting(settings,"title");
+ for (auto a: attributes){
+ if (find_setting(settings,a.first,"")!="") {
+ attributes[a.first]->init(find_setting(settings,a.first,""));
+ }
+ }
+ }
+ void update(const Time_spec &time){
+ for (auto p: parameters){
+ p.second->get(time);
+ }
}
virtual void set_parameter(const std::string &key,const std::string &value){};
- virtual void link_params(){}; //TODO make param classes that link automatically
- void update_params(const Time_spec& time);
};
class Signal_node: public Node{
- public:
- virtual ~Signal_node(){};
- const float get_output(const Time_spec &time) { update_params(time); return output(time); };
- virtual const float output(const Time_spec &time) { return 0.0f; };
+ public:
+ virtual ~Signal_node(){};
+ const float get_output(const Time_spec &time) {
+ update(time);
+ return output(time);
+ };
+ virtual const float output(const Time_spec &time) { return 0.0f; };
};
class Image_node: public Node{
public:
virtual ~Image_node(){};
vector<Image_input*> image_inputs; //image node also has image inputs and outputs
- void create_image_input(const string &description) {image_inputs.push_back(new Image_input(description));};
- Image *get_output(const Frame_spec &frame) { update_params((Time_spec)frame); return output(frame); };
+ void create_image_input(const string &_title,const string &_desc,Node* _connect=nullptr) {
+ image_inputs.push_back(new Image_input(_desc,_title,_connect));
+ };
+ Image *get_output(const Frame_spec &frame) {
+ image.setup(frame.w,frame.h);
+ update((Time_spec)frame);
+ return output(frame);
+ }
virtual const Image *output(const Frame_spec &frame)=0;
Image *get_preview(const Frame_spec &frame);
- Image *image; //this can be privately allocated or just passed on as the node see fit
- private:
- float image_time;
+ Image image;
+ private:
+ float image_time; //? could be used to detect image reuse?
+
};
- class Base_audio_processor: public Signal_node {
+ class Audio_processor: public Signal_node {
public:
- virtual ~Base_audio_processor(){};
+ virtual Audio_processor(){};
+ virtual ~Audio_processor(){};
virtual int process_frame(uint8_t *data,int samples)=0;
virtual bool init(int _channels,int _bits,int _samples,int _rate)=0;
virtual void cleanup()=0;
@@ -282,8 +231,11 @@ namespace Rotor {
//actual nodes-------------------------------------------------
class Time: public Signal_node {
public:
- Time(){};
- Time(map<string,string> &settings) {
+ Time(){
+ title="Time";
+ description="Outputs the time in seconds as a signal";
+ };
+ Time(map<string,string> &settings): Time() {
base_settings(settings);
};
Time* clone(map<string,string> &_settings) { return new Time(_settings);};
@@ -293,8 +245,11 @@ namespace Rotor {
};
class Track_time: public Signal_node {
public:
- Track_time(){};
- Track_time(map<string,string> &settings) {
+ Track_time(){
+ title="Track time";
+ description="Outputs the fraction of the track as a signal";
+ };
+ Track_time(map<string,string> &settings): Track_time() {
base_settings(settings);
};
Track_time* clone(map<string,string> &_settings) { return new Track_time(_settings);};
@@ -304,287 +259,230 @@ namespace Rotor {
};
class Signal_output: public Signal_node {
public:
- Signal_output(){};
- Signal_output(map<string,string> &settings) {
+ Signal_output(){
+ create_signal_input("signal","Signal Input");
+ title="Signal output";
+ description="Outputs a signal to xml for testing";
+ };
+ Signal_output(map<string,string> &settings): Signal_output() {
base_settings(settings);
};
Signal_output* clone(map<string,string> &_settings) { return new Signal_output(_settings);};
bool render(const float duration, const float framerate,string &xml_out);
const float output(const Time_spec &time) {
- if (inputs[0]->connection) {
- return ((Signal_node*)(inputs[0]->connection))->get_output(time);
- }
- else return 0.0f;
+ return inputs[0]->get(time);
}
};
class Testcard: public Image_node {
public:
- Testcard(){image=nullptr;};
- Testcard(map<string,string> &settings) {
+ Testcard(){
+ //internal testing node only
+ };
+ Testcard(map<string,string> &settings): Testcard() {
base_settings(settings);
- image=new Image();
};
- ~Testcard(){ if (image) delete image;};
+ ~Testcard(){};
Testcard* clone(map<string,string> &_settings) { return new Testcard(_settings);};
Image *output(const Frame_spec &frame){
- if (image->setup(frame.w,frame.h)) {
-
- }
- //always create testcard
- //float ws=(255.0f/frame.w);
float hs=(255.0f/frame.h);
for (int i=0;i<frame.h;i++){
for (int j=0;j<frame.w;j++){
- image->RGBdata[(i*frame.w+j)*3]=(uint8_t)((int)((i+(frame.time*25.0f)*hs))%255);
- image->RGBdata[((i*frame.w+j)*3)+1]=(uint8_t)((int)((j+(frame.time*100.0f)*hs))%255);
- image->RGBdata[((i*frame.w+j)*3)+2]=(uint8_t)(0);
+ image.RGBdata[(i*frame.w+j)*3]=(uint8_t)((int)((i+(frame.time*25.0f)*hs))%255);
+ image.RGBdata[((i*frame.w+j)*3)+1]=(uint8_t)((int)((j+(frame.time*100.0f)*hs))%255);
+ image.RGBdata[((i*frame.w+j)*3)+2]=(uint8_t)(0);
//image->Adata[i*frame.w+j]=(uint8_t)255;
//image->Zdata[i*frame.w+j]=(uint16_t)512; //1.0 in fixed point 8.8 bits
}
}
- return image;
+ return &image;
}
private:
- Image *image; //is an image generator
+
};
class Invert: public Image_node {
public:
Invert(){
- image=nullptr;
- create_image_input("image to invert");
+ create_image_input("Image to invert","Image input");
+ create_parameter("invert","number","Invert when greater than 0.0","Negative",1.0f,0.0f,1.0f);
+ title="Negative";
+ description="Inverts the input picture";
};
- Invert(map<string,string> &settings) {
+ Invert(map<string,string> &settings) :Invert() {
base_settings(settings);
- image=new Image();
};
- ~Invert(){ if (image) delete image;};
+ ~Invert(){};
Invert* clone(map<string,string> &_settings) { return new Invert(_settings);};
Image *output(const Frame_spec &frame){
- if (image_inputs.size()) {
- if (image_inputs[0]->connection){
- if (inputs[0]->connection) {
- if (fgreater_or_equal(1.0f,(((Signal_node*)inputs[0]->connection)->get_output((Time_spec)frame)))) {
- Image *in=(((Image_node*)image_inputs[0]->connection)->get_output(frame));
- if (in){
- image->setup(frame.w,frame.h);
- for (int i=0;i<in->w*in->h*3;i++) {
- image->RGBdata[i]=255-in->RGBdata[i];
- }
- return image;
- }
- }
- }
- return (((Image_node*)image_inputs[0]->connection)->get_output(frame));
- }
-
- }
- if (image_inputs[0]->connection) {
- return image;
- }
+ Image *in=image_inputs[0]->get(frame);
+ if (in) {
+ if (parameters["invert"]->value>0.0f){
+ for (int i=0;i<in->w*in->h*3;i++) {
+ image.RGBdata[i]=255-in->RGBdata[i];
+ }
+ return &image;
+ }
+ return in;
+ }
return nullptr;
}
private:
- Image *image; //is an image generator
- //bool invert;
};
class Video_cycler: public Image_node {
- //cycles through video inputs in order
public:
- Video_cycler(){create_image_input("duplicatable");};
- Video_cycler(map<string,string> &settings) {
+ Video_cycler(){
+ create_image_input("Image input","Image input");
+ create_signal_input("Selector","Selector input");
+ title="Video cycler";
+ description="Cycles through video inputs according to selector signal";
+ duplicate_inputs=true;
+ }
+ Video_cycler(map<string,string> &settings):Video_cycler() {
base_settings(settings);
};
~Video_cycler(){};
bool load(const string &filename);
Image *output(const Frame_spec &frame){
- int which_input=0;
- if (inputs[0]->connection) {
- which_input=((int)((Signal_node*)inputs[0]->connection)->get_output((Time_spec)frame))%image_inputs.size();
- }
- if (image_inputs.size()) {
- if (image_inputs[which_input]->connection){
- return (((Image_node*)image_inputs[which_input]->connection)->get_output(frame));
- }
- }
- return nullptr;
+ return image_inputs[((int)inputs[0]->get((Time_spec)frame))%image_inputs.size()]->get(frame);
}
Video_cycler* clone(map<string,string> &_settings) { return new Video_cycler(_settings);};
- private:
};
class Signal_colour: public Image_node {
- //cycles through video inputs in order
public:
Signal_colour(){
- create_signal_input("colour selector");
+ create_signal_input("Selector","Selector input");
+ create_attribute("colours","palette list of web colours","Colours","000000");
+ title="Signal colour";
+ description="Cycles through a palette of background colours according to selector signal";
};
- Signal_colour(map<string,string> &settings) {
+ Signal_colour(map<string,string> &settings):Signal_colour() {
base_settings(settings);
- string colours=find_setting(settings,"palette","");
- for (int i=0;i<colours.size()/6;i++){
- palette.push_back(Colour(colours.substr(i*6,6)));
- }
- for (auto i: palette) {
- cerr << "Signal_colour found palette colour: "<<(int)i.r<<" "<<(int)i.g<<" "<<(int)i.b<<endl;
+ for (int i=0;i<attributes["colours"]->value.size()/6;i++){
+ palette.push_back(Colour(attributes["colours"]->value.substr(i*6,6)));
}
prevcol=-1;
};
~Signal_colour(){};
Image *output(const Frame_spec &frame){
if (palette.size()) {
- if (inputs.size()) {
- if (inputs[0]->connection){
- int col= ((int)(((Signal_node*)inputs[0]->connection)->get_output(frame)))%palette.size();
- if (col!=prevcol||image.w!=frame.w||image.h!=frame.h){
- image.setup(frame.w,frame.h);
- for (int i=0;i<image.w*image.h;i++){
- image.RGBdata[i*3]=palette[col].r;
- image.RGBdata[i*3+1]=palette[col].g;
- image.RGBdata[i*3+2]=palette[col].b;
- }
- prevcol=col;
- }
- return &image;
+ int col=((int)inputs[0]->get((Time_spec)frame))%palette.size();
+ if (col!=prevcol){ //how about when starting a new render?
+ for (int i=0;i<image.w*image.h;i++){
+ image.RGBdata[i*3]=palette[col].r;
+ image.RGBdata[i*3+1]=palette[col].g;
+ image.RGBdata[i*3+2]=palette[col].b;
}
+ prevcol=col;
}
+ return &image;
}
return nullptr;
}
Signal_colour* clone(map<string,string> &_settings) { return new Signal_colour(_settings);};
private:
vector<Rotor::Colour> palette;
- Image image;
int prevcol;
};
class Signal_greyscale: public Image_node {
//Draws signal bars in greyscale
public:
- Signal_greyscale(){};
- Signal_greyscale(map<string,string> &settings) {
+ Signal_greyscale(){
+ create_signal_input("Signal","Signal input");
+ title="Signal greyscale";
+ description="Renders signal level (0..1) as greyscale background";
+ };
+ Signal_greyscale(map<string,string> &settings):Signal_greyscale() {
base_settings(settings);
prevcol=-1;
};
~Signal_greyscale(){};
Image *output(const Frame_spec &frame){
- if (inputs.size()) {
- if (inputs[0]->connection){
- float sig= ((((Signal_node*)inputs[0]->connection)->get_output(frame)));
- uint8_t col=255-((uint8_t)(sig*255.0f));
- if (col!=prevcol||image.w!=frame.w||image.h!=frame.h){
- image.setup(frame.w,frame.h);
- for (int i=0;i<image.w*image.h*3;i++){
- image.RGBdata[i]=col;
- }
- prevcol=col;
- }
- return &image;
+ uint8_t col=255-((uint8_t)(inputs[0]->get((Time_spec)frame)*255.0f));
+ if (col!=prevcol){ //how about when starting a new render?
+ for (int i=0;i<image.w*image.h*3;i++){
+ image.RGBdata[i]=col;
}
+ prevcol=col;
}
- return nullptr;
+ return &image;
+
}
Signal_greyscale* clone(map<string,string> &_settings) { return new Signal_greyscale(_settings);};
private:
- Image image;
uint8_t prevcol;
};
-#define ARITHMETIC_plus 1
-#define ARITHMETIC_minus 2
-#define ARITHMETIC_multiply 3
-#define ARITHMETIC_divide 4
-#define ARITHMETIC_modulo 5
+ #define ARITHMETIC_plus 1
+ #define ARITHMETIC_minus 2
+ #define ARITHMETIC_multiply 3
+ #define ARITHMETIC_divide 4
+ #define ARITHMETIC_modulo 5
class Image_arithmetic: public Image_node {
- //Draws signal bars in greyscale
public:
- Image_arithmetic(){image=nullptr;};
- Image_arithmetic(map<string,string> &settings) {
+ Image_arithmetic(){
+ create_image_input("image input","Image input");
+ create_parameter("value","number","Value or signal for operation","Value",1.0f);
+ create_attribute("operator","operator for image","Operator","+",{"+","-","*","/"});
+ title="Image arithmetic";
+ description="Performs arithmetic on an image with a signal or value";
+ };
+ Image_arithmetic(map<string,string> &settings):Image_arithmetic() {
base_settings(settings);
- value=find_setting(settings,"value",0.0f);
- string _op=find_setting(settings,"operator","+");
- if (_op=="+"||_op=="plus"||_op=="add") op=ARITHMETIC_plus;
- if (_op=="-"||_op=="minus"||_op=="subtract") op=ARITHMETIC_minus;
- if (_op=="*"||_op=="x"||_op=="multiply") op=ARITHMETIC_multiply;
- if (_op=="/"||_op=="divide") op=ARITHMETIC_divide;
- //if (_op=="%"||_op=="mod"||_op=="modulo"||_op=="modulus") op=ARITHMETIC_modulo; ??what would this even mean?
- image=nullptr;
}
- void link_params() {
- for (auto p:parameter_inputs){
- if (p->parameter=="value") {
- p->receiver=&value;
- }
- }
-
- };
- ~Image_arithmetic(){if (image) delete image;};
+ ~Image_arithmetic(){};
Image *output(const Frame_spec &frame){
- if (image_inputs.size()) {
- if (image_inputs[0]->connection){
- if (image) delete image; //from the previous frame- this may not be ideal
- //because operator* made a new image it should be deleted
- Image *in=(((Image_node*)image_inputs[0]->connection)->get_output(frame));
- switch (op) {
- case ARITHMETIC_plus:
- image=(*in)+value;
- break;
- case ARITHMETIC_minus:
- image=(*in)-value;
- break;
- case ARITHMETIC_multiply:
- image=(*in)*value;
- break;
- case ARITHMETIC_divide:
- image=(*in)/value;
- break;
- }
- return image;
- }
+ Image *in=image_inputs[0]->get(frame);
+ if (in){
+ switch (attributes["operator"]->intVal) {
+ case ARITHMETIC_plus:
+ image=(*in); //could be poss without copy?
+ image+=parameters["value"]->value;
+ break;
+ case ARITHMETIC_minus:
+ image=(*in);
+ image-=parameters["value"]->value;
+ break;
+ case ARITHMETIC_multiply:
+ image=(*in);
+ image*=parameters["value"]->value;
+ break;
+ case ARITHMETIC_divide:
+ image=(*in);
+ image/=parameters["value"]->value;
+ break;
+ }
}
- return nullptr;
+ return &image;
}
Image_arithmetic* clone(map<string,string> &_settings) { return new Image_arithmetic(_settings);};
private:
- Image *image;
- float value;
- int op;
};
-
class Luma_levels: public Image_node {
- //applies LUT To RGB channels equally
public:
- Luma_levels(){LUT=nullptr;image=nullptr;};
- Luma_levels(map<string,string> &settings) {
- base_settings(settings);
- levels_settings(settings);
- image=new Image();
- }
- void link_params() {
- for (auto p:parameter_inputs){
- if (p->parameter=="black_in") p->receiver=&black_in;
- if (p->parameter=="white_in") p->receiver=&white_in;
- if (p->parameter=="gamma") p->receiver=&gamma;
- if (p->parameter=="black_out") p->receiver=&black_out;
- if (p->parameter=="white_out") p->receiver=&white_out;
- }
- };
- ~Luma_levels(){if (LUT) {delete[] LUT;} if (image) delete image; };
- void levels_settings(map<string,string> &settings){
- black_in=find_setting(settings,"black_in",0.0f);
- white_in=find_setting(settings,"white_in",1.0f);
- gamma=find_setting(settings,"gamma",1.0f);
- black_out=find_setting(settings,"black_out",0.0f);
- white_out=find_setting(settings,"white_out",1.0f);
+ Luma_levels(){
+ create_image_input("image input","Image input");
+ create_parameter("black_in","number","input black point (0..1)","Input black point",0.0f,0.0f,1.0f);
+ create_parameter("white_in","number","input white point (0..1)","Input white point",1.0f,0.0f,1.0f);
+ create_parameter("gamma","number","gamma level","Gamma",1.0f,0.0f,10.0f);
+ create_parameter("black_out","number","output black point (0..1)","Output black point",0.0f,0.0f,1.0f);
+ create_parameter("white_out","number","output white point (0..1)","Output white point",1.0f,0.0f,1.0f);
+ title="Luma levels";
+ description="Remap luma values of image";
LUT=nullptr;
- generate_LUT();
+ };
+ Luma_levels(map<string,string> &settings):Luma_levels() {
+ base_settings(settings);
}
- void generate_LUT(){ //check this
+ ~Luma_levels(){if (LUT) { delete[] LUT;} };
+ void generate_LUT(){
+ //can check here if anything has changed
if (LUT) delete[] LUT;
LUT=new unsigned char[256];
float fltmax=(255.0f/256.0f);
for (int i=0;i<256;i++){
- LUT[i]=(unsigned char)(((pow(min(fltmax,max(0.0f,(((((float)i)/256.0f)-black_in)/(white_in-black_in)))),(1.0/gamma))*(white_out-black_out))+black_out)*256.0f);
+ LUT[i]=(unsigned char)(((pow(min(fltmax,max(0.0f,(((((float)i)/256.0f)-parameters["black_in"]->value)/(parameters["white_in"]->value-parameters["black_in"]->value)))),(1.0/parameters["gamma"]->value))*(parameters["white_out"]->value-parameters["black_out"]->value))+parameters["black_out"]->value)*255.0f);
}
}
void apply_LUT(const Image& in){
- apply_LUT(in,*image);
+ apply_LUT(in,image);
}
void apply_LUT(const Image& in,Image &out){ //facility to apply to other images for inherited classes
out.setup(in.w,in.h);
@@ -593,21 +491,16 @@ namespace Rotor {
}
}
Image *output(const Frame_spec &frame){
- if (image_inputs.size()) {
- if (image_inputs[0]->connection){
- if (LUT) {
- apply_LUT(*(((Image_node*)image_inputs[0]->connection)->get_output(frame)));
- return image;
- }
- }
+ Image *in=image_inputs[0]->get(frame);
+ if (in){
+ generate_LUT();
+ apply_LUT(*in);
}
- return nullptr;
+ return &image;
}
Luma_levels* clone(map<string,string> &_settings) { return new Luma_levels(_settings);};
protected:
unsigned char *LUT;
- Image *image;
- float black_in,white_in,gamma,black_out,white_out;
};
class Echo_trails: public Luma_levels {
//draw trail frames additively that fade off over time
@@ -635,38 +528,25 @@ namespace Rotor {
//or is it actually best to use alpha keying after all!
public:
- Echo_trails(){image=nullptr;};
- Echo_trails(map<string,string> &settings) {
+ Echo_trails(){
+ //calls base class constructor first
+ create_parameter("number","number","number of echoes","Number echoes",25.0f);
+ create_parameter("fadeto","number","amount that echoes fade out (0..1)","Fadout amount",1.0f,0.0f,1.0f);
+ create_attribute("mode","blend mode for echoes","Blend mode","screen",{"screen","wrap"});
+ title="Echo trails";
+ description="Draw trail frames additively that fade off over time";
+ };
+ Echo_trails(map<string,string> &settings):Echo_trails() {
base_settings(settings);
- //duration=find_setting(settings,"duration",1.0f);
- number=find_setting(settings,"number",1);
- fadeto=find_setting(settings,"fadeto",1.0f);
- levels_settings(settings);
- image=nullptr;
lastframe=-1;
- mode=find_setting(settings,"mode",0.0f);
}
- void link_params() {
- for (auto p:parameter_inputs){
- if (p->parameter=="black_in") p->receiver=&black_in;
- if (p->parameter=="white_in") p->receiver=&white_in;
- if (p->parameter=="gamma") p->receiver=&gamma;
- if (p->parameter=="black_out") p->receiver=&black_out;
- if (p->parameter=="white_out") p->receiver=&white_out;
-
- //TODO: control an integer
- if (p->parameter=="mode") p->receiver=&mode;
- }
- };
- //~Echo_trails(){if (image) {delete image;} };
~Echo_trails(){
- if (image) delete image;
- for (auto i:images) {if (image) delete i.second;}
+ for (auto i:images) delete i.second;
};
Image *output(const Frame_spec &frame){
//check if cache is valid
if (images.size()){
- if (frame.w!=image->w||frame.h!=image->h){ //or framerate changed?
+ if (frame.w!=image.w||frame.h!=image.h){ //or framerate changed?
//clear cache and start over
images.clear();
lastframe=-1;
@@ -680,7 +560,7 @@ namespace Rotor {
auto i = std::begin(images);
while (i != std::end(images)) {
// check if the image is in the range we need
- if (thisframe-(*i).first>number||thisframe-(*i).first<0) {
+ if (thisframe-(*i).first>(int)parameters["number"]->value||thisframe-(*i).first<0) {
delete (*i).second;
i = images.erase(i);
}
@@ -688,144 +568,113 @@ namespace Rotor {
++i;
}
//if frame has already been calculated just return it
- if (thisframe==lastframe) {
- return image;
- }
- else {
- if (image_inputs.size()) {
- if (image_inputs[0]->connection){
- if (LUT) {
- //need a better strategy here, should be able to get each image once
- //copy incoming image **writable
- if (image) image->free();
- image=(((Image_node*)image_inputs[0]->connection)->get_output(frame))->clone();
- images[thisframe]=new Image(frame.w,frame.h);
- apply_LUT(*(image),*(images[thisframe]));
- for (int i=1;i<number;i++){
- //check echo frame isn't at negative time
- int absframe=thisframe-i;
- if (absframe>-1){
- //check if image is in the cache
- if (images.find(absframe)==images.end()){
- images[absframe]=new Image(frame.w,frame.h);
- Frame_spec wanted=Frame_spec(absframe,frame.framerate,frame.duration,frame.w,frame.h);
- apply_LUT(*(((Image_node*)image_inputs[0]->connection)->get_output(wanted)),*(images[absframe]));
- }
- //cerr<<"Rotor: about to apply image ("<<images[absframe].w<<"x"<<images[absframe].h<<")"<<endl;
- if (fless(1.0f,fadeto)){
- float amount=((((float)number-i)/number)*(1.0f-fadeto))+(1.0f-fadeto);
- Image *temp=*images[absframe]*amount;
- if (mode<0.5) {
- (*image)+=*temp;
- }
- else {
- image->add_wrap(*temp);
- }
- delete temp;
- }
- else {
- if (mode<0.5) (*image)+=*(images[absframe]);
- else (*image)=image->add_wrap(*(images[absframe]));
- }
+ if (thisframe!=lastframe) {
+ Image *in=image_inputs[0]->get(frame);
+ if (in) {
+ generate_LUT();
+ //need a better strategy here, should be able to get each image once
+ //copy incoming image **writable
+ image=*(in);
+ images[thisframe]=new Image(frame.w,frame.h);
+ apply_LUT(image,*(images[thisframe]));
+ for (int i=1;i<(int)parameters["number"]->value;i++){
+ //check echo frame isn't at negative time
+ int absframe=thisframe-i;
+ if (absframe>-1){
+ //check if image is in the cache
+ if (images.find(absframe)==images.end()){
+ images[absframe]=new Image(frame.w,frame.h);
+ Frame_spec wanted=Frame_spec(absframe,frame.framerate,frame.duration,frame.w,frame.h);
+ apply_LUT(*(((Image_node*)image_inputs[0]->connection)->get_output(wanted)),*(images[absframe]));
+ }
+ if (fless(1.0f,parameters["fadeto"]->value)){
+ float amount=(((parameters["number"]->value-i)/parameters["number"]->value)*(1.0f-parameters["fadeto"]->value))+(1.0f-parameters["fadeto"]->value);
+ Image *temp=*images[absframe]*amount;
+ if (attributes["mode"]->value=="screen") {
+ image+=*temp;
+ }
+ else {
+ image.add_wrap(*temp);
}
+ delete temp;
+ }
+ else {
+ if (attributes["mode"]->value=="screen") image+=*(images[absframe]);
+ else image=image.add_wrap(*(images[absframe]));
}
- //for (int i=0;i<frame.w*frame.h*3;i++){
- // image->RGBdata[i]=LUT[in->RGBdata[i]];
- //}
- lastframe=thisframe;
- return image;
}
}
+ //for (int i=0;i<frame.w*frame.h*3;i++){
+ // image->RGBdata[i]=LUT[in->RGBdata[i]];
+ //}
+ lastframe=thisframe;
}
}
- return nullptr;
+ return &image;
}
Echo_trails* clone(map<string,string> &_settings) { return new Echo_trails(_settings);};
protected:
- float duration,fadeto;
- int number;
int interval,total,lastframe; //number of frames between displayed echoes
unordered_map<int,Image*> images;
- float mode; //TODO make int, enum string parameter types
};
- #define BLEND_screen 1
- #define BLEND_multiply 2
- #define BLEND_blend 3
+ #define BLEND_blend 1
+ #define BLEND_screen 2
+ #define BLEND_multiply 3
#define BLEND_alpha 4
- #define BLEND_screen_wrap 5
- #define BLEND_multiply_wrap 6
- #define BLEND_xor 7
+ #define BLEND_wrap 5
+ #define BLEND_xor 6
class Blend: public Image_node {
public:
- Blend(){image=nullptr;};
- Blend(map<string,string> &settings) {
- base_settings(settings);
- image=nullptr;
- amount=find_setting(settings,"amount",1.0f);
- string _mode=find_setting(settings,"mode","screen");
- if (_mode=="screen") mode=BLEND_screen;
- if (_mode=="multiply") mode=BLEND_multiply;
- if (_mode=="blend") mode=BLEND_blend;
- if (_mode=="alpha") mode=BLEND_alpha;
- if (_mode=="screen_wrap") mode=BLEND_screen_wrap;
- if (_mode=="multiply_wrap") mode=BLEND_multiply_wrap;
- if (_mode=="xor") mode=BLEND_xor;
+ Blend(){
+ create_image_input("image input 1","Image input 1");
+ create_image_input("image input 2","Image input 2");
+ create_parameter("amount","number","amount to blend input 2","Blend amount",0.5f,0.0f,1.0f);
+ create_attribute("mode","Blend mode","Blend mode","blend",{"blend","screen","multiply","alpha","wrap","xor"});
+ title ="Blend";
+ description="Blend images in various modes";
};
- void link_params() {
- for (auto p:parameter_inputs){
- if (p->parameter=="amount") p->receiver=&amount;
- }
+ Blend(map<string,string> &settings):Blend() {
+ base_settings(settings);
};
- ~Blend(){ if (image) delete image;};
+ ~Blend(){};
Blend* clone(map<string,string> &_settings) { return new Blend(_settings);};
Image *output(const Frame_spec &frame){
- if (image_inputs.size()) {
- if (image_inputs[0]->connection){
- if (image_inputs.size()>1) {
- if (image_inputs[1]->connection) {
- //copy incoming image **writable
- if (image) delete image;
- image=(((Image_node*)image_inputs[0]->connection)->get_output(frame))->clone();
- switch(mode){
- case BLEND_screen:
- (*image)+=(*(((Image_node*)image_inputs[1]->connection)->get_output(frame)));
- break;
- case BLEND_multiply:
- (*image)*=(*(((Image_node*)image_inputs[1]->connection)->get_output(frame)));
- break;
- case BLEND_xor:
- (*image)^=(*(((Image_node*)image_inputs[1]->connection)->get_output(frame)));
- break;
- case BLEND_alpha:
- (*image)=(*image).alpha_blend(*(((Image_node*)image_inputs[1]->connection)->get_output(frame)));
- break;
- case BLEND_screen_wrap:
- (*image)=(*image).add_wrap(*(((Image_node*)image_inputs[1]->connection)->get_output(frame)));
- break;
- case BLEND_multiply_wrap:
- (*image)=(*image).divide_wrap(*(((Image_node*)image_inputs[1]->connection)->get_output(frame)));
- break;
- case BLEND_blend: //has to be last because of initialser of *in? go figure
- (*image)*=(1.0f-amount);
- Image *in=(*(((Image_node*)image_inputs[1]->connection)->get_output(frame)))*amount;
- (*image)+=(*in);
- delete in;
- break;
-
- }
- return image;
- }
- }
- //if there aren't 2 image inputs connected just return the first
- return (((Image_node*)image_inputs[0]->connection)->get_output(frame));
- }
- }
+ Image *in1=image_inputs[0]->get(frame);
+ if (in1){
+ Image *in2=image_inputs[1]->get(frame);
+ if (in2) {
+ image=*(in1);
+ switch(attributes["mode"]->intVal){
+ case BLEND_screen:
+ image+=(*in2);
+ break;
+ case BLEND_multiply:
+ image*=(*in2);
+ break;
+ case BLEND_xor:
+ image^=(*in2);
+ break;
+ case BLEND_alpha:
+ image=image.alpha_blend(*in2);
+ break;
+ case BLEND_wrap:
+ image=image.add_wrap(*in2);
+ break;
+ case BLEND_blend: //has to be last because of initialser of *in? go figure
+ image*=(1.0f-parameters["amount"]->value);
+ Image *in=(*in2)*parameters["amount"]->value;
+ image+=(*in);
+ delete in;
+ break;
+ }
+ return &image;
+ }
+ //if there aren't 2 image inputs connected just return the first
+ return in1;
+ }
return nullptr;
}
private:
- Image *image; //is an image generator
- int mode;
- float amount; //for blend
};
#define MIRROR_horiz 1
#define MIRROR_vert 2
@@ -833,99 +682,95 @@ namespace Rotor {
#define MIRROR_vertR 4
class Mirror: public Image_node {
public:
- Mirror(){image=nullptr;};
- Mirror(map<string,string> &settings) {
+ Mirror(){
+ create_image_input("image input","Image input");
+ create_attribute("mode","Mirror mode","Mirror mode","horiz",{"horiz","vert","horizR","vertR"});
+ title="Mirror";
+ description="Mirror video across a central axis";
+ };
+ Mirror(map<string,string> &settings):Mirror() {
base_settings(settings);
- image=nullptr;
- string _mode=find_setting(settings,"mode","horiz");
- if (_mode=="horiz") mode=MIRROR_horiz;
- if (_mode=="vert") mode=MIRROR_vert;
- if (_mode=="horizR") mode=MIRROR_horizR;
- if (_mode=="vertR") mode=MIRROR_vertR;
};
- ~Mirror(){ if (image) delete image;};
+ ~Mirror(){ };
Mirror* clone(map<string,string> &_settings) { return new Mirror(_settings);};
Image *output(const Frame_spec &frame){
- if (image_inputs.size()) {
- if (image_inputs[0]->connection){
- //copy incoming image **writable
- if (image) delete image;
- image=(((Image_node*)image_inputs[0]->connection)->get_output(frame))->clone();
- switch (mode) {
- case MIRROR_horiz:
- for (int i=0;i<image->w/2;i++){
- for (int j=0;j<image->h;j++){
- for (int k=0;k<3;k++){
- image->RGBdata[(((j*image->w)+((image->w/2)+i))*3)+k]=image->RGBdata[(((j*image->w)+((image->w/2)-i))*3)+k];
- }
+ Image *in=image_inputs[0]->get(frame);
+ if (in){
+ //copy incoming image **writable
+ image=(*in);
+ //could be more efficient here by only copying once
+ switch (attributes["mode"]->intVal) {
+ case MIRROR_horiz:
+ for (int i=0;i<image.w/2;i++){
+ for (int j=0;j<image.h;j++){
+ for (int k=0;k<3;k++){
+ image.RGBdata[(((j*image.w)+((image.w/2)+i))*3)+k]=image.RGBdata[(((j*image.w)+((image.w/2)-i))*3)+k];
}
}
- break;
- case MIRROR_vert:
- for (int i=0;i<image->w;i++){
- for (int j=0;j<image->h/2;j++){
- for (int k=0;k<3;k++){
- image->RGBdata[((((image->h/2+j)*image->w)+i)*3)+k]=image->RGBdata[((((image->h/2-j)*image->w)+i)*3)+k];
- }
+ }
+ break;
+ case MIRROR_vert:
+ for (int i=0;i<image.w;i++){
+ for (int j=0;j<image.h/2;j++){
+ for (int k=0;k<3;k++){
+ image.RGBdata[((((image.h/2+j)*image.w)+i)*3)+k]=image.RGBdata[((((image.h/2-j)*image.w)+i)*3)+k];
}
}
- break;
- case MIRROR_horizR:
- for (int i=0;i<image->w/2;i++){
- for (int j=0;j<image->h;j++){
- for (int k=0;k<3;k++){
- image->RGBdata[(((j*image->w)+((image->w/2)-i))*3)+k]=image->RGBdata[(((j*image->w)+((image->w/2)+i))*3)+k];
- }
+ }
+ break;
+ case MIRROR_horizR:
+ for (int i=0;i<image.w/2;i++){
+ for (int j=0;j<image.h;j++){
+ for (int k=0;k<3;k++){
+ image.RGBdata[(((j*image.w)+((image.w/2)-i))*3)+k]=image.RGBdata[(((j*image.w)+((image.w/2)+i))*3)+k];
}
}
- break;
- case MIRROR_vertR:
- for (int i=0;i<image->w;i++){
- for (int j=0;j<image->h/2;j++){
- for (int k=0;k<3;k++){
- image->RGBdata[((((image->h/2-j)*image->w)+i)*3)+k]=image->RGBdata[((((image->h/2+j)*image->w)+i)*3)+k];
- }
+ }
+ break;
+ case MIRROR_vertR:
+ for (int i=0;i<image.w;i++){
+ for (int j=0;j<image.h/2;j++){
+ for (int k=0;k<3;k++){
+ image.RGBdata[((((image.h/2-j)*image.w)+i)*3)+k]=image.RGBdata[((((image.h/2+j)*image.w)+i)*3)+k];
}
}
- break;
- }
- return image;
- }
+ }
+ break;
+ }
+ return &image;
}
return nullptr;
}
private:
- Image *image; //is an image generator
- int mode;
};
class Monochrome: public Image_node {
public:
- Monochrome(){};
- Monochrome(map<string,string> &settings) {
+ Monochrome(){
+ create_image_input("image input","Image input");
+ title="Monochrome";
+ description="Render video greyscale";
+ };
+ Monochrome(map<string,string> &settings):Monochrome() {
base_settings(settings);
};
~Monochrome(){
};
Monochrome* clone(map<string,string> &_settings) { return new Monochrome(_settings);};
Image *output(const Frame_spec &frame){
- if (image_inputs.size()) {
- if (image_inputs[0]->connection){
- Image *other=(((Image_node*)image_inputs[0]->connection)->get_output(frame));
- image.setup(other->w,other->h);
- for (int i=0;i<image.w;i++){
- for (int j=0;j<image.h;j++){
- uint8_t luma=0;
- for (int l=0;l<3;l++) luma+=pixels.mono_weights[l][other->RGBdata[(((j*image.w)+i)*3)+l]];
- for (int k=0;k<3;k++) image.RGBdata[(((j*image.w)+i)*3)+k]=luma;
- }
+ Image *in=image_inputs[0]->get(frame);
+ if (in){
+ for (int i=0;i<image.w;i++){
+ for (int j=0;j<image.h;j++){
+ uint8_t luma=0;
+ for (int l=0;l<3;l++) luma+=pixels.mono_weights[l][in->RGBdata[(((j*image.w)+i)*3)+l]];
+ for (int k=0;k<3;k++) image.RGBdata[(((j*image.w)+i)*3)+k]=luma;
}
- return &image;
- }
+ }
+ return &image;
}
return nullptr;
}
private:
- Image image;
};
class Transform: public Image_node {
//what is the best coordinate system to use?
@@ -933,133 +778,128 @@ namespace Rotor {
//units: pixel or fractional
//aspect: scaled or homogenous
public:
- Transform(){};
- Transform(map<string,string> &settings) {
+ Transform(){
+ create_image_input("image input","Image input");
+ create_parameter("transformX","number","X transformation","Transform X",0.0f);
+ create_parameter("transformY","number","Y transformation","Transform X",0.0f);
+ create_parameter("originX","number","X transformation origin","Origin X",0.5f);
+ create_parameter("originY","number","Y transformation origin","Origin Y",0.5f);
+ create_parameter("rotation","number","Rotation about origin","Rotation",0.0f);
+ create_parameter("scale","number","Scale about origin","Scale",1.0f);
+ title="Transform";
+ description="Apply 2D transformation";
+ };
+ Transform(map<string,string> &settings):Transform() {
base_settings(settings);
- tX=find_setting(settings,"transformX",0.0f);
- tY=find_setting(settings,"transformY",0.0f);
- oX=find_setting(settings,"originX",0.5f);
- oY=find_setting(settings,"originX",0.5f);
- r=find_setting(settings,"rotation",0.0f);
- s=find_setting(settings,"scale",1.0f);
};
~Transform(){
};
- void link_params() {
- for (auto p:parameter_inputs){
- if (p->parameter=="scale") p->receiver=&s;
- if (p->parameter=="rotation") p->receiver=&r;
- if (p->parameter=="transformX") p->receiver=&tX;
- if (p->parameter=="transformY") p->receiver=&tY;
- if (p->parameter=="originX") p->receiver=&oX;
- if (p->parameter=="originY") p->receiver=&oY;
- }
- };
Transform* clone(map<string,string> &_settings) { return new Transform(_settings);};
Image *output(const Frame_spec &frame){
- if (image_inputs.size()) {
- if (image_inputs[0]->connection){
- Image *other=(((Image_node*)image_inputs[0]->connection)->get_output(frame));
- if (other) {
- image.setup(other->w,other->h);
- //do opencv transform
- cv::Point2f srcTri[3], dstTri[3];
- cv::Mat rot_mat(2,3,CV_32FC1);
- cv::Mat trans_mat(2,3,CV_32FC1);
+ Image *in=image_inputs[0]->get(frame);
+ if (in){
+ float tX=parameters["transformX"]->value;
+ float tY=parameters["transformY"]->value;
+ float oX=parameters["originX"]->value;
+ float oY=parameters["originY"]->value;
+ float r=parameters["rotation"]->value;
+ float s=parameters["scale"]->value;
+ //do opencv transform
+ cv::Point2f srcTri[3], dstTri[3];
+ cv::Mat rot_mat(2,3,CV_32FC1);
+ cv::Mat trans_mat(2,3,CV_32FC1);
- Image inter;
- inter.setup(other->w,other->h);
- // Compute matrix by creating triangle and transforming
- //is there a better way - combine the 2? Just a bit of geometry
- srcTri[0].x=0;
- srcTri[0].y=0;
- srcTri[1].x=other->w-1;
- srcTri[1].y=0;
- srcTri[2].x=0;
- srcTri[2].y=other->h-1;
- for (int i=0;i<3;i++){
- dstTri[i].x=srcTri[i].x+(tX*other->w);
- dstTri[i].y=srcTri[i].y+(tY*other->h);
- }
- trans_mat=getAffineTransform( srcTri, dstTri );
- warpAffine( other->rgb, inter.rgb, trans_mat, inter.rgb.size(), cv::INTER_LINEAR, cv::BORDER_WRAP);
+ Image inter;
+ inter.setup(in->w,in->h);
+ // Compute matrix by creating triangle and transforming
+ //is there a better way - combine the 2? Just a bit of geometry
+ srcTri[0].x=0;
+ srcTri[0].y=0;
+ srcTri[1].x=in->w-1;
+ srcTri[1].y=0;
+ srcTri[2].x=0;
+ srcTri[2].y=in->h-1;
+ for (int i=0;i<3;i++){
+ dstTri[i].x=srcTri[i].x+(tX*in->w);
+ dstTri[i].y=srcTri[i].y+(tY*in->h);
+ }
+ trans_mat=getAffineTransform( srcTri, dstTri );
+ warpAffine( in->rgb, inter.rgb, trans_mat, inter.rgb.size(), cv::INTER_LINEAR, cv::BORDER_WRAP);
- // Compute rotation matrix
- //
- cv::Point centre = cv::Point( oX*other->w, oY*other->h );
+ // Compute rotation matrix
+ //
+ cv::Point centre = cv::Point( oX*in->w, oY*in->h );
- rot_mat = getRotationMatrix2D( centre, r, s );
- // Do the transformation
- //
- warpAffine( inter.rgb, image.rgb, rot_mat, image.rgb.size(), cv::INTER_LINEAR, cv::BORDER_WRAP);
- //BORDER_WRAP
+ rot_mat = getRotationMatrix2D( centre, r, s );
+ // Do the transformation
+ //
+ warpAffine( inter.rgb, image.rgb, rot_mat, image.rgb.size(), cv::INTER_LINEAR, cv::BORDER_WRAP);
+ //BORDER_WRAP
- //INTER_NEAREST - a nearest-neighbor interpolation
- //INTER_LINEAR - a bilinear interpolation (used by default)
- //INTER_AREA - resampling using pixel area relation. It may be a preferred method for image decimation, as it gives moire’-free results. But when the image is zoomed, it is similar to the INTER_NEAREST method.
- //INTER_CUBIC - a bicubic interpolation over 4x4 pixel neighborhood
- //INTER_LANCZOS4 - a Lanczos interpolation over 8x8 pixel neighborhood
+ //INTER_NEAREST - a nearest-neighbor interpolation
+ //INTER_LINEAR - a bilinear interpolation (used by default)
+ //INTER_AREA - resampling using pixel area relation. It may be a preferred method for image decimation, as it gives moire’-free results. But when the image is zoomed, it is similar to the INTER_NEAREST method.
+ //INTER_CUBIC - a bicubic interpolation over 4x4 pixel neighborhood
+ //INTER_LANCZOS4 - a Lanczos interpolation over 8x8 pixel neighborhood
- return &image;
- }
+ return &image;
}
- }
return nullptr;
}
private:
- Image image;
- float tX,tY,oX,oY,r,s;
//todo - quality settings
};
class Alpha_merge: public Image_node {
public:
- Alpha_merge(){image=nullptr;};
- Alpha_merge(map<string,string> &settings) {
+ Alpha_merge(){
+ create_image_input("image input","Image input");
+ create_image_input("alpha input","Alpha input");
+ create_parameter("transformX","number","X transformation","Transform X",0.0f);
+ title="Alpha merge";
+ description="Alpha merge two images";
+ };
+ Alpha_merge(map<string,string> &settings):Alpha_merge() {
base_settings(settings);
- image=nullptr;
};
- ~Alpha_merge(){ if (image) delete image;};
+ ~Alpha_merge(){};
Alpha_merge* clone(map<string,string> &_settings) { return new Alpha_merge(_settings);};
Image *output(const Frame_spec &frame){
- if (image_inputs.size()) {
- if (image_inputs[0]->connection){
- //copy incoming image **writable
- if (image) delete image;
- image=(((Image_node*)image_inputs[0]->connection)->get_output(frame))->clone();
- if (image_inputs.size()>1) {
- if (image_inputs[1]->connection) {
- image->alpha_merge(*((Image_node*)image_inputs[1]->connection)->get_output(frame));
- }
- }
- //if there aren't 2 image inputs connected just return the first
- return image;
- }
- }
+ Image *in1=image_inputs[0]->get(frame);
+ if (in1){
+ //copy incoming image **writable
+ Image *in2=image_inputs[1]->get(frame);
+ if (in2) {
+ image=(*in1);
+ image.alpha_merge(*in2);
+ return &image;
+ }
+ //if there aren't 2 image inputs connected just return the first
+ return in1;
+ }
return nullptr;
}
private:
- Image *image; //is an image generator
};
#define VIDEOFRAMES_still 1
#define VIDEOFRAMES_blend 2
class Video_loader: public Image_node {
public:
- Video_loader(){};
- Video_loader(map<string,string> &settings) {
+ Video_loader(){
+ create_parameter("speed","number","video playback speed","Speed",1.0f,0.0f,0.0f);
+ create_parameter("framerate","number","framerate override","Frame rate",0.0f,0.0f,0.0f);
+ create_attribute("filename","name of video file to load","File name","");
+ create_attribute("mode","frame mode","Mode","still",{"still","blend"});
+ title="Video loader";
+ description="Loads a video file";
+ };
+ Video_loader(map<string,string> &settings): Video_loader() {
base_settings(settings);
isLoaded=false;
- filename=find_setting(settings,"filename","");
- speed=find_setting(settings,"speed",1.0f);
- framerate=find_setting(settings,"framerate",0.0f);
- //0.0f signifies to use the internal framerate
- if (filename!="") {
- load(find_setting(settings,"media_path","")+filename);
+ if (attributes["filename"]->value!="") {
+ load(find_setting(settings,"media_path","")+attributes["filename"]->value);
}
- string frame_op=find_setting(settings,"mode","still");
- if (frame_op=="still") mode=VIDEOFRAMES_still;
- if (frame_op=="blend") mode=VIDEOFRAMES_blend;
lastframe=0;
};
~Video_loader(){};
@@ -1069,55 +909,22 @@ namespace Rotor {
bool isLoaded;
private:
libav::decoder player;
- Image image;
- string filename;
- float speed;
- float framerate;
int mode;
int lastframe;
};
- /*
- class CVideo_loader: public Image_node {
-
- // attempt
-
- // /usr/bin/ld: warning: libavcodec.so.53, needed by /usr/lib/gcc/i686-linux-gnu/4.7/../../../../lib/libopencv_highgui.so, may conflict with libavcodec.so.55
- // /usr/bin/ld: warning: libavformat.so.53, needed by /usr/lib/gcc/i686-linux-gnu/4.7/../../../../lib/libopencv_highgui.so, may conflict with libavformat.so.55
- // /usr/bin/ld: warning: libavutil.so.51, needed by /usr/lib/gcc/i686-linux-gnu/4.7/../../../../lib/libopencv_highgui.so, may conflict with libavutil.so.52
-
- // No URL Protocols are registered. Missing call to av_register_all()?
- // libav::Error: file:///mnt/rotor/media/newsins1_360.mp4 Protocol not found
- // Rotor::Video_loader: failed to load /mnt/rotor/media/newsins1_360.mp4
- // 30-07-2013 09:35:31 Rotor: ERROR: could not load newsins1_360.mp4 into video node 03
-
- public:
- CVideo_loader(){};
- CVideo_loader(map<string,string> &settings) {
- base_settings(settings);
- isLoaded=false;
- };
- ~CVideo_loader(){};
- bool load(const string &filename);
- Image *output(const Frame_spec &frame);
- CVideo_loader* clone(map<string,string> &_settings) { return new CVideo_loader(_settings);};
- bool isLoaded;
- private:
- cv::VideoCapture player;
- Image image;
- };
- */
class Video_output: public Image_node {
public:
- Video_output(){};
- Video_output(map<string,string> &settings) {
+ Video_output(){
+ create_image_input("image to output","Image input");
+ title="Video output";
+ description="Outputs to video from here";
+ };
+ Video_output(map<string,string> &settings):Video_output() {
base_settings(settings);
};
~Video_output(){ };
Image *output(const Frame_spec &frame){
- if (image_inputs[0]->connection) {
- return ((Image_node*)(image_inputs[0]->connection))->get_output(frame);
- }
- else return nullptr;
+ return image_inputs[0]->get(frame);
};
Video_output* clone(map<string,string> &_settings) { return new Video_output(_settings);};
bool render(const float duration, const float framerate,const string &output_filename,const string &audio_filename,float& progress,int w,int h);
@@ -1125,7 +932,7 @@ namespace Rotor {
private:
};
- //-------------------------------------------------------------------
+ //-------------------------------------------------------------------
class Node_factory{
public:
Node_factory();
@@ -1146,26 +953,71 @@ namespace Rotor {
void list_nodes(xmlIO XML){
int i=0;
for (auto& type: type_map) { //c++11
- XML.addTag("node");
- XML.addAttribute("node","name",type.first,i);
- if (dynamic_cast<Signal_node*> (type.second)!=nullptr) XML.addAttribute("node","type","signal",i);
- if (dynamic_cast<Image_node*> (type.second)!=nullptr) XML.addAttribute("node","type","image",i);
- XML.pushTag("node",i);
- int j=0;
- for (auto& input: type.second->inputs) {
- XML.addTag("signal_input");
- XML.setValue("signal_input",input->description,j);
- j++;
- }
- if (dynamic_cast<Image_node*> (type.second)!=nullptr) {
- for (auto& input: (dynamic_cast<Image_node*>(type.second))->image_inputs) {
- XML.addTag("image_input");
- XML.setValue("image_input",input->description,j);
+ if (type.second->description!="") { //blank description = internal/ testing node
+ XML.addTag("node");
+ XML.addAttribute("node","type",type.first,i);
+ XML.addAttribute("node","inputs",type.second->duplicate_inputs?"expandable":"fixed",i);
+ XML.addAttribute("node","title",type.second->title,i);
+ XML.addAttribute("node","description",type.second->description,i);
+ if (dynamic_cast<Signal_node*> (type.second)!=nullptr) XML.addAttribute("node","output","signal",i);
+ if (dynamic_cast<Image_node*> (type.second)!=nullptr) XML.addAttribute("node","output","image",i);
+ XML.pushTag("node",i);
+ //if (type.second->description!="") {
+ // XML.addTag("description");
+ // XML.setValue("description",type.second->description,0);
+ //}
+ int j=0;
+ for (auto& input: type.second->inputs) {
+ XML.addTag("signal_input");
+ XML.addAttribute("signal_input","title",input->title,j);
+ XML.addAttribute("signal_input","description",input->description,j);
j++;
}
+ j=0;
+ if (dynamic_cast<Image_node*> (type.second)!=nullptr) {
+ for (auto& input: (dynamic_cast<Image_node*>(type.second))->image_inputs) {
+ XML.addTag("image_input");
+ XML.addAttribute("image_input","title",input->title,j);
+ XML.addAttribute("image_input","description",input->description,j);
+ j++;
+ }
+ }
+ j=0;
+ for (auto& parameter: type.second->parameters) {
+ XML.addTag("parameter");
+ XML.addAttribute("parameter","name",parameter.first,j);
+ XML.addAttribute("parameter","type",parameter.second->type,j);
+ XML.addAttribute("parameter","title",parameter.second->title,j);
+ XML.addAttribute("parameter","description",parameter.second->description,j);
+ XML.addAttribute("parameter","value",parameter.second->value,j);
+ XML.addAttribute("parameter","min",parameter.second->min,j);
+ XML.addAttribute("parameter","max",parameter.second->max,j);
+ j++;
+ }
+ j=0;
+ for (auto& attribute: type.second->attributes) {
+ XML.addTag("attribute");
+ XML.addAttribute("attribute","name",attribute.first,j);
+ XML.addAttribute("attribute","title",attribute.second->title,j);
+ XML.addAttribute("attribute","description",attribute.second->description,j);
+ XML.addAttribute("attribute","value",attribute.second->value,j);
+ if (attribute.second->vals.size()){ //document attribute enumeration
+ XML.addAttribute("attribute","type","enum",j);
+ XML.pushTag("attribute",j);
+ int k=0;
+ for (auto val: attribute.second->vals){
+ XML.addTag("option");
+ XML.addAttribute("option","value",val,k);
+ k++;
+ }
+ XML.popTag();
+ }
+ else XML.addAttribute("attribute","type","string",j);
+ j++;
+ }
+ XML.popTag();
+ i++;
}
- XML.popTag();
- i++;
}
}
private:
@@ -1190,12 +1042,10 @@ namespace Rotor {
Node* find_node(const string &type);
bool signal_render(string &signal_xml,const float framerate);
bool video_render(const string &output_filename,const string &audio_filename,const float framerate,float& progress);
- int load(Poco::UUID uid);
bool load(string data,string media_path);
bool loadFile(string &filename,string media_path);
bool parseXml(string media_path);
bool set_resolution(int w,int h);
- UUID save(); //save to DB, returns UUID of saved graph
bool loaded;
float duration;
const string toString();
@@ -1204,7 +1054,7 @@ namespace Rotor {
Node_factory factory;
int outW,outH;
};
- class Audio_thumbnailer: public Base_audio_processor {
+ class Audio_thumbnailer: public Audio_processor {
public:
Audio_thumbnailer(){
height=128;
@@ -1226,62 +1076,6 @@ namespace Rotor {
int offset;
double scale,accum;
};
- class Render_context: public Poco::Task { //Poco task object
- //manages a 'patchbay'
- //high level interfaces for the wizard
- //and low level interface onto the graph
- public:
- Render_context(const std::string& name): Task(name) {
- audio_thumb=new Audio_thumbnailer();
- state=IDLE;
- output_framerate=25.0f;
- audio_loaded=false;
-
- xmlIO xml;
- if(xml.loadFile("settings.xml") ){
- graph_dir=xml.getAttribute("Rotor","graph_dir","",0);
- media_dir=xml.getAttribute("Rotor","media_dir","",0);
- output_dir=xml.getAttribute("Rotor","output_dir","",0);
- }
-
- else cerr<<"Rotor: settings.xml not found, using defaults"<<endl;
- };
- ~Render_context(){delete audio_thumb;};
- void runTask();
- void add_queue(int item);
- Command_response session_command(const std::vector<std::string>& command);
- void session_command(const std::vector<std::string>& command,xmlIO& XML,HTTPResponse::HTTPStatus& status);
- Render_status get_status();
- void cancel(); //interrupt locking process
- int make_preview(int nodeID, float time); //starts a frame preview - returns status code - how to retrieve?
- bool load_audio(const string &filename,vector<Base_audio_processor*> processors);
- bool _load_audio(const string &filename,vector<Base_audio_processor*> processors);
- Render_requirements get_requirements();
- bool load_video(const string &nodeID,const string &filename);//can be performance or clip
- private:
- int state;
- float progress; //for a locking process: audio analysis or rendering
- //thread only does one thing at once
- std::deque<int> work_queue;
- Poco::Mutex mutex; //lock for access from parent thread
- std::string audio_filename;
- std::string output_filename;
- std::string graph_dir;
- std::string media_dir;
- std::string output_dir;
-
- Audio_thumbnailer *audio_thumb;
- Graph graph;
- Node_factory factory;
- float output_framerate;
- bool audio_loaded;
-
- };
}
-/*
-coding style
-Types begin with capitals 'New_type'
-variables/ instances use lower case with underscore as a seperator
-*/
-#endif
+#endif \ No newline at end of file
diff --git a/rotord/src/rotord.h b/rotord/src/rotord.h
index 7656c28..5d4398b 100755
--- a/rotord/src/rotord.h
+++ b/rotord/src/rotord.h
@@ -6,6 +6,7 @@
#include "Poco/Net/HTTPServerResponse.h"
#include "Poco/Net/HTTPServerParams.h"
#include "Poco/Net/ServerSocket.h"
+#include "Poco/UUIDGenerator.h"
#include "Poco/Timestamp.h"
#include "Poco/DateTimeFormatter.h"
#include "Poco/DateTimeFormat.h"
@@ -67,7 +68,7 @@ using Poco::Message;
using Poco::AutoPtr;
-#include "rotor.h"
+#include "rendercontext.h"
class RenderContextHandler: public HTTPRequestHandler
diff --git a/rotord/src/tinyxml.cpp b/rotord/src/tinyxml.cpp
index 0c79683..9b26b96 100755
--- a/rotord/src/tinyxml.cpp
+++ b/rotord/src/tinyxml.cpp
@@ -1790,7 +1790,7 @@ bool TiXmlPrinter::VisitEnter( const TiXmlElement& element, const TiXmlAttribute
&& element.FirstChild()->ToText()->CDATA() == false )
{
simpleTextPrint = true;
- // no DoLineBreak()!
+ //DoLineBreak()!
}
else
{
@@ -1846,7 +1846,7 @@ bool TiXmlPrinter::Visit( const TiXmlText& text )
}
else
{
- DoIndent();
+ //DoIndent();
TIXML_STRING str;
TiXmlBase::EncodeString( text.ValueTStr(), &str );
buffer += str;