diff options
Diffstat (limited to 'rotord/src/rotor.h')
| -rwxr-xr-x | rotord/src/rotor.h | 1391 |
1 files changed, 1391 insertions, 0 deletions
diff --git a/rotord/src/rotor.h b/rotord/src/rotor.h new file mode 100755 index 0000000..f922fcf --- /dev/null +++ b/rotord/src/rotor.h @@ -0,0 +1,1391 @@ +#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 +*/ + +#include <unordered_map> +#include <deque> +#include <math.h> +#include <memory> +#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 "xmlIO.h" +#include "utils.h" //fequal +#include "libavwrapper.h" +#include "cvimage.h" + +namespace Rotor { + #define IDLE 0 + #define ANALYSING_AUDIO 1 + #define AUDIO_READY 2 + #define CREATING_PREVIEW 3 + #define PREVIEW_READY 4 + #define RENDERING 5 + #define RENDER_READY 6 + + #define ANALYSE_AUDIO 1 + #define PREVIEW 2 + #define RENDER 3 + + //forward declaration + 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; + } + + ~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 framerate; + float duration; + Time_spec lastframe() const{ + return Time_spec(time-(1.0f/framerate),framerate,duration); + } + }; + 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; + int h,w; + //Frame_spec lastframe(){ + // return Frame_spec(time-(1.0f/framerate),framerate,w,h); + //} + int frame(){ + return (int)((time*framerate)+0.5); //rounded to the nearest frame + } + }; + class Colour{ + public: + Colour(){ + r=g=b=0; + } + Colour(int c){ + r=c&0xFF; + g=(c&0xFF00)>>8; + b=(c&0xFF0000)>>16; + } + Colour(std::string s){ + r=(uint8_t)ofHexToChar(s.substr(0,2)); + g=(uint8_t)ofHexToChar(s.substr(2,2)); + b=(uint8_t)ofHexToChar(s.substr(4,2)); + } + 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; } + std::string description; + Poco::Net::HTTPResponse::HTTPStatus status; + }; + class Input{ + public: + Input(const string &_desc): connection(nullptr),description(_desc){}; + Node* connection; + string description; + }; + class Image_input: public Input{ + public: + bool connect(Image_node *source); + Image_input(const string &_desc): Input(_desc){}; + }; + class Signal_input: public Input{ + public: + bool connect(Signal_node *source); + Signal_input(const string &_desc): Input(_desc){}; + }; + class Parameter_input: 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; + }; + 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));}; + string description; + string type; + string output_type; + string ID; + 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"); + } + 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; }; + }; + 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); }; + 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; + }; + class Base_audio_processor: public Signal_node { + public: + virtual ~Base_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; + virtual void print_summary(){}; + int channels,bits,samples,rate; + }; + //actual nodes------------------------------------------------- + class Time: public Signal_node { + public: + Time(){}; + Time(map<string,string> &settings) { + base_settings(settings); + }; + Time* clone(map<string,string> &_settings) { return new Time(_settings);}; + const float output(const Time_spec &time) { + return time.time; + } + }; + class Track_time: public Signal_node { + public: + Track_time(){}; + Track_time(map<string,string> &settings) { + base_settings(settings); + }; + Track_time* clone(map<string,string> &_settings) { return new Track_time(_settings);}; + const float output(const Time_spec &time) { + return time.time/time.duration; + } + }; +#define COMPARISON_Equal 1 +#define COMPARISON_Not_equal 2 +#define COMPARISON_Greater 3 +#define COMPARISON_Less 4 +#define COMPARISON_Greater_or_equal 5 +#define COMPARISON_Less_or_equal 6 + class Comparison: public Signal_node { + public: + Comparison(){}; + Comparison(map<string,string> &settings) { + 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; + } + } + } + return 0.0f; + } + int op; + float value; + }; +#define ARITHMETIC_plus 1 +#define ARITHMETIC_minus 2 +#define ARITHMETIC_multiply 3 +#define ARITHMETIC_divide 4 +#define ARITHMETIC_modulo 5 + class Arithmetic: public Signal_node { + public: + Arithmetic(){}; + Arithmetic(map<string,string> &settings) { + base_settings(settings); + value=find_setting(settings,"value",0.0f); + string _op=find_setting(settings,"operator","+"); + if (_op=="+") op=ARITHMETIC_plus; + if (_op=="-") op=ARITHMETIC_minus; + if (_op=="*") op=ARITHMETIC_multiply; + if (_op=="/") op=ARITHMETIC_divide; + if (_op=="%") op=ARITHMETIC_modulo; + } + 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 (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 ARITHMETIC_plus: + return in+value; + break; + case ARITHMETIC_minus: + return in-value; + break; + case ARITHMETIC_multiply: + return in*value; + break; + case ARITHMETIC_divide: + return in/value; + break; + case ARITHMETIC_modulo: + return fmod(in,value); + break; + } + } + } + return 0.0f; + } + 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=÷_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) { + 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; + } + } + return 0.0f; + } + }; + class On_off: public Signal_node { + public: + On_off(){}; + On_off(map<string,string> &settings) { + base_settings(settings); + }; + On_off* clone(map<string,string> &_settings) { return new On_off(_settings);}; + const float output(const Time_spec &time) { + if (inputs[0]->connection) { + float s1=(((Signal_node*)(inputs[0]->connection))->get_output(time)); + if ((int)s1%2) return 1.0f; + } + return 0.0f; + } + }; + //pseudo random repeatable hash function + //http://create.stephan-brumme.com/fnv-hash/ + const uint32_t Prime = 0x01000193; // 16777619 + const uint32_t Seed = 0x811C9DC5; // 2166136261 + /// hash a byte + inline uint32_t fnv1a(unsigned char oneByte, uint32_t hash = Seed) + { + return (oneByte ^ hash) * Prime; + } + /// hash a short (two bytes) + inline uint32_t fnv1a(unsigned short twoBytes, uint32_t hash = Seed) + { + const unsigned char* ptr = (const unsigned char*) &twoBytes; + hash = fnv1a(*ptr++, hash); + return fnv1a(*ptr , hash); + } + /// hash a 32 bit integer (four bytes) + inline uint32_t fnv1a(uint32_t fourBytes, uint32_t hash = Seed) + { + const unsigned char* ptr = (const unsigned char*) &fourBytes; + hash = fnv1a(*ptr++, hash); + hash = fnv1a(*ptr++, hash); + hash = fnv1a(*ptr++, hash); + return fnv1a(*ptr , hash); + } + class Random: public Signal_node { + public: + Random(){}; + Random(map<string,string> &settings) { + base_settings(settings); + seed=(Seed+find_setting(settings,"seed",0)); + cerr<<"random:: seed "<<seed<<" ("<<Seed<<")"<<endl; + }; + 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; + private: + }; + class Signal_output: public Signal_node { + public: + Signal_output(){}; + Signal_output(map<string,string> &settings) { + 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; + } + }; + class Testcard: public Image_node { + public: + Testcard(){image=nullptr;}; + Testcard(map<string,string> &settings) { + base_settings(settings); + image=new Image(); + }; + ~Testcard(){ if (image) delete image;}; + 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->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; + } + private: + Image *image; //is an image generator + }; + class Invert: public Image_node { + public: + Invert(){image=nullptr;}; + Invert(map<string,string> &settings) { + base_settings(settings); + image=new Image(); + }; + ~Invert(){ if (image) delete image;}; + 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; + } + return nullptr; + } + private: + Image *image; //is an image generator + //bool invert; + }; + class Video_output: public Image_node { + public: + Video_output(){}; + Video_output(map<string,string> &settings) { + 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; + }; + 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); + + private: + + }; + class Video_loader: public Image_node { + public: + Video_loader(){}; + Video_loader(map<string,string> &settings) { + base_settings(settings); + isLoaded=false; + }; + ~Video_loader(){}; + bool load(const string &filename); + Image *output(const Frame_spec &frame); + Video_loader* clone(map<string,string> &_settings) { return new Video_loader(_settings);}; + bool isLoaded; + private: + libav::decoder player; + Image image; + }; + class Video_cycler: public Image_node { + //cycles through video inputs in order + public: + Video_cycler(){}; + Video_cycler(map<string,string> &settings) { + 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; + } + 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(){}; + Signal_colour(map<string,string> &settings) { + 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; + } + 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 ℑ + } + } + } + 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) { + 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 ℑ + } + } + return nullptr; + } + Signal_greyscale* clone(map<string,string> &_settings) { return new Signal_greyscale(_settings);}; + private: + Image image; + uint8_t prevcol; + }; + class Image_arithmetic: public Image_node { + //Draws signal bars in greyscale + public: + Image_arithmetic(){image=nullptr;}; + Image_arithmetic(map<string,string> &settings) { + base_settings(settings); + value=find_setting(settings,"value",0.0f); + string _op=find_setting(settings,"operator","+"); + if (_op=="+") op=ARITHMETIC_plus; + if (_op=="-") op=ARITHMETIC_minus; + if (_op=="*") op=ARITHMETIC_multiply; + if (_op=="/") op=ARITHMETIC_divide; + //if (_op=="%") op=ARITHMETIC_modulo; ??what would this even mean? + image=nullptr; + cerr<<"image_arithmetic: mode "<<op<<", value "<<value<<endl; + } + void link_params() { + for (auto p:parameter_inputs){ + if (p->parameter=="value") { + p->receiver=&value; + } + } + + }; + ~Image_arithmetic(){if (image) delete image;}; + 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; + } + } + return nullptr; + } + 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); + LUT=nullptr; + generate_LUT(); + } + void generate_LUT(){ //check this + 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); + } + } + void apply_LUT(const Image& in){ + 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); + for (int i=0;i<out.w*out.h*3;i++){ + out.RGBdata[i]=LUT[in.RGBdata[i]]; + } + } + 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; + } + } + } + return nullptr; + } + 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 + //the hard thing here is how to cache frames, if its done cleverly it could have no impact when + //used linearly + //Image needs to overload operator+ + //need a clever data structure to cache frames - maybe a map of Image pointers + + //we know the frames we want to overlay as offsets ie -25,-20,-15,-10,-5 + //do we keep 25 frames loaded in order to benefit? 25 PAL frames is 60MB so probably so + //OK so: + //make a new set of pointers + //identify if any of the new pointers can inherit old frames + //delete unneeded old frames + //load new frames + //do the calculations + + //new set of pointers? or track frames by absolute frame number? + //with relative pointers and switching frames, could use auto_ptr? + + //this cache mechanism should maybe be inheritable too? + + //it could be hugely beneficial to only do the LUT once? + //although maybe the way to do the fading is to have a LUT for each frame? + + //or is it actually best to use alpha keying after all! + public: + Echo_trails(){image=nullptr;}; + Echo_trails(map<string,string> &settings) { + 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;} + }; + 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? + //clear cache and start over + images.clear(); + lastframe=-1; + //calculate frame interval + //interval=(int)(((duration/number)*frame.framerate)+0.5); + //total=interval*number; + } + } + int thisframe=frame.frame(); + //iterate cache and throw out any obsolete frames + 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) { + delete (*i).second; + i = images.erase(i); + } + else + ++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])); + } + } + } + //for (int i=0;i<frame.w*frame.h*3;i++){ + // image->RGBdata[i]=LUT[in->RGBdata[i]]; + //} + lastframe=thisframe; + return image; + } + } + } + } + return nullptr; + } + 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_alpha 4 + #define BLEND_screen_wrap 5 + #define BLEND_multiply_wrap 6 + #define BLEND_xor 7 + 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; + }; + void link_params() { + for (auto p:parameter_inputs){ + if (p->parameter=="amount") p->receiver=&amount; + } + }; + ~Blend(){ if (image) delete image;}; + 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)); + } + } + return nullptr; + } + private: + Image *image; //is an image generator + int mode; + float amount; //for blend + }; + #define MIRROR_horiz 1 + #define MIRROR_vert 2 + #define MIRROR_horizR 3 + #define MIRROR_vertR 4 + class Mirror: public Image_node { + public: + Mirror(){image=nullptr;}; + Mirror(map<string,string> &settings) { + 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* 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]; + } + } + } + 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_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; + } + } + return nullptr; + } + private: + Image *image; //is an image generator + int mode; + }; + class Monochrome: public Image_node { + public: + Monochrome(){}; + Monochrome(map<string,string> &settings) { + 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; + } + } + return ℑ + } + } + return nullptr; + } + private: + Image image; + }; + class Transform: public Image_node { + //what is the best coordinate system to use? + //origin: corner or centre + //units: pixel or fractional + //aspect: scaled or homogenous + public: + Transform(){}; + Transform(map<string,string> &settings) { + 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 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); + + + // Compute rotation matrix + // + cv::Point centre = cv::Point( oX*other->w, oY*other->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 + + //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 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) { + base_settings(settings); + }; + ~Alpha_merge(){ if (image) delete image;}; + 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; + } + } + return nullptr; + } + private: + Image *image; //is an image generator + }; + //------------------------------------------------------------------- + class Node_factory{ + public: + Node_factory(); + ~Node_factory(){ + for (auto t:type_map) delete t.second; + } + void add_type(string type,Node* proto){ + type_map[type]=proto; + }; + Node *create(map<string,string> &settings){ + if (settings.find("type")!=settings.end()) { + if (type_map.find(settings["type"])!=type_map.end()) { + return type_map[settings["type"]]->clone(settings); + } + } + return NULL; + }; + private: + unordered_map<string,Node*> type_map; + }; + class Graph{ + public: + Graph(){duration=20.0f;loaded = false;outW=640;outH=360;}; + Graph(const string& _uid,const string& _desc){init(_uid,_desc);}; + void init(const string& _uid,const string& _desc){ uid=_uid;description=_desc;duration=20.0f;}; + string uid; //every version of a graph has a UUID, no particular need to actually read its data(?) + //?? is it faster than using strings?? + string description; + std::unordered_map<string,Node*> nodes; + vector<Node*> find_nodes(const string &type); //could be a way of finding a set based on capabilities? + 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); + bool loadFile(string &filename); + bool parseXml(); + bool set_resolution(int w,int h); + UUID save(); //save to DB, returns UUID of saved graph + bool loaded; + float duration; + const string toString(); + xmlIO xml; + private: + Node_factory factory; + int outW,outH; + }; + class Audio_thumbnailer: public Base_audio_processor { + public: + Audio_thumbnailer(){ + height=128; + width=512; //fit + data=new uint8_t[height*width]; + memset(data,0,height*width); + }; + ~Audio_thumbnailer(){ + delete[] data; + }; + Audio_thumbnailer* clone(map<string,string> &_settings) { return new Audio_thumbnailer();}; + bool init(int _channels,int _bits,int _samples,int _rate); + void cleanup(){}; + int process_frame(uint8_t *data,int samples_in_frame); + string print(); + uint8_t *data; + int height,width,samples_per_column; + int column,out_sample,sample,samples; + 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
\ No newline at end of file |
