diff options
Diffstat (limited to 'rotord/src/rotor.h')
| -rwxr-xr-x | rotord/src/rotor.h | 1314 |
1 files changed, 554 insertions, 760 deletions
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 ¶meter,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 ℑ } 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 ℑ + } + 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 ℑ + 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 ℑ } 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 ℑ + 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 ℑ + } 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_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=γ - 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 ℑ } 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=γ - 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 ℑ } 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 ℑ + } + //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 ℑ } 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 ℑ - } + } + return ℑ } 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 ℑ - } + return ℑ } - } 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 ℑ + } + //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 |
