#ifndef ROTOR_NODES_CHANNELS #define ROTOR_NODES_CHANNELS #include "rotor.h" namespace Rotor { class Invert: public Image_node { public: 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"; NODEID="8676c25c-2d09-11e3-80a7-db36c774523c"; }; Invert(map &settings) :Invert() { base_settings(settings); }; ~Invert(){}; Invert* clone(map &_settings) { return new Invert(_settings);}; Image *output(const Frame_spec &frame){ Image *in=image_inputs[0]->get(frame); if (in) { if (parameters["invert"]->value>0.0f){ for (int i=0;iw*in->h*3;i++) { image.RGBdata[i]=255-in->RGBdata[i]; } return ℑ } return in; } return nullptr; } private: }; class Monochrome: public Image_node { public: Monochrome(){ create_image_input("image input","Image input"); title="Monochrome"; description="Render video greyscale"; NODEID="2c3cb12e-2d0a-11e3-a46b-a34e44493cef"; }; Monochrome(map &settings):Monochrome() { base_settings(settings); }; ~Monochrome(){ }; Monochrome* clone(map &_settings) { return new Monochrome(_settings);}; Image *output(const Frame_spec &frame){ Image *in=image_inputs[0]->get(frame); if (in){ for (int i=0;iRGBdata[(((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: }; #define BLEND_blend 1 #define BLEND_screen 2 #define BLEND_multiply 3 #define BLEND_alpha 4 #define BLEND_wrap 5 #define BLEND_xor 6 #define BLEND_overlay 7 #define BLEND_min 8 #define BLEND_max 9 class Blend: public Image_node { public: 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","overlay","min","max"}); title ="Blend"; description="Blend images in various modes"; NODEID="12ed7af0-2d0a-11e3-ae32-2b44203b93c9"; }; Blend(map &settings):Blend() { base_settings(settings); }; ~Blend(){}; Blend* clone(map &_settings) { return new Blend(_settings);}; Image *output(const Frame_spec &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_overlay: image=image.overlay(*in2); break; case BLEND_min: image=image.min(*in2); break; case BLEND_max: image=image.max(*in2); break; case BLEND_blend: //has to be last because of initialser of *in? go figure image*=(1.0f-parameters["amount"]->value); /* //problem here with leak //opencv handles not being released Image *in=(*in2)*parameters["amount"]->value; image+=(*in); delete in; */ in=(*in2); //removed allocator in*=parameters["amount"]->value; image+=in; break; } return ℑ } //if there aren't 2 image inputs connected just return the first return in1; } return nullptr; } private: Image in; }; #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 { public: 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"; NODEID="bc3b633e-2d09-11e3-86b2-7fbba3d71604"; }; Image_arithmetic(map &settings):Image_arithmetic() { base_settings(settings); } ~Image_arithmetic(){}; Image *output(const Frame_spec &frame){ 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 ℑ } Image_arithmetic* clone(map &_settings) { return new Image_arithmetic(_settings);}; private: }; class Alpha_merge: public Image_node { public: Alpha_merge(){ create_image_input("image input","Image input"); create_image_input("alpha input","Alpha input"); title="Alpha merge"; description="Alpha merge two images"; NODEID="3f5e3eee-2d0a-11e3-8679-1374154a9fa8"; }; Alpha_merge(map &settings):Alpha_merge() { base_settings(settings); }; ~Alpha_merge(){}; Alpha_merge* clone(map &_settings) { return new Alpha_merge(_settings);}; Image *output(const Frame_spec &frame){ 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: }; class Difference_matte: public Image_node { public: Difference_matte(){ create_image_input("image input","Image input"); create_image_input("background input","Background input"); create_parameter("threshold","number","Difference threshold","Threshold",0.2f,0.0f,1.0f); create_parameter("feather","number","Feather width","Feather",0.1f,0.0f,1.0f); create_parameter("weight_h","number","H component weight","Weight H",0.5f,0.0f,1.0f); create_parameter("weight_s","number","S component weight","Weight S",0.5f,0.0f,1.0f); create_parameter("weight_v","number","V component weight","Weight V",0.5f,0.0f,1.0f); create_parameter("blursize","number","Blur size","Blur size",2.0f,0.0f,10.0f); create_attribute("mode","Output {image|alpha}","output mode","alpha",{"image","alpha"}); title="Difference matte"; description="Create an alpha channel using a background reference picture"; LUT=nullptr; NODEID="4db4d2c8-2d0a-11e3-b08b-7fb00f8c562a"; }; Difference_matte(map &settings):Difference_matte() { base_settings(settings); }; ~Difference_matte(){if (LUT) delete[] LUT;}; Difference_matte* clone(map &_settings) { return new Difference_matte(_settings);}; Image *output(const Frame_spec &frame){ Image *in1=image_inputs[0]->get(frame); if (in1){ Image *in2=image_inputs[1]->get(frame); if (in2) { generate_LUT(); /* cv::cvtColor(in1->rgb,greyfg,CV_RGB2GRAY); cv::cvtColor(in2->rgb,greybg,CV_RGB2GRAY); cv::absdiff(greyfg,greybg,greyDiff); //parameters["threshold"]->value cv::threshold(greyDiff,mask,parameters["threshold"]->value,255,CV_THRESH_BINARY); //int block_size=3, double param1=5); //int blockSize, int offset=0,bool invert=false, bool gauss=false); //cv::adaptiveThreshold(greyDiff,mask,255,CV_ADAPTIVE_THRESH_GAUSSIAN_C,CV_THRESH_BINARY, 3,5); //int block_size=3, double param1=5); //int blockSize, int offset=0,bool invert=false, bool gauss=false); */ cv::cvtColor(in1->rgb, hsv1, CV_RGB2HSV); cv::cvtColor(in2->rgb, hsv2, CV_RGB2HSV); mask.create(frame.h,frame.w,CV_8UC1); lutmask.create(frame.h,frame.w,CV_8UC1); //get euclidean distance in HSV space int dist,d; float weights[3] = {parameters["weight_h"]->value,parameters["weight_s"]->value,parameters["weight_v"]->value}; float weight_total=255.0f/pow(pow(weights[0]*255,2)+pow(weights[1]*255,2)+pow(weights[2]*255,2),0.5); for (int i=0;ivalue/2.0)*2)+1,1.0); //nb this doesn't do the intended: create 'continuously variable' blur cv::GaussianBlur(mask,filtmask,cvSize(ksize,ksize),parameters["blursize"]->value); for (int i=0;ivalue=="image"){ cv::cvtColor(lutmask, image.rgb, CV_GRAY2RGB); } else image.alpha_from_cv(lutmask); return ℑ } //if there aren't 2 image inputs connected just return the first return in1; } return nullptr; } void generate_LUT(){ //can check here if anything has changed //cerr<<"generating LUT: threshold "<value<<", feather "<value<value-(parameters["feather"]->value*0.5f)); float maxf=min(1.0f,parameters["threshold"]->value+(parameters["feather"]->value*0.5f)); for (int i=0;i<256;i++){ LUT[i]=(uint8_t)(min(1.0f,max(0.0f,((((float)i)/255.0f)-minf)/(maxf-minf)))*255.0f); // cerr<<((int)LUT[i])<<" "; } //cerr< &settings):Luma_levels() { base_settings(settings); } ~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)-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); } 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;iget(frame); if (in){ generate_LUT(); apply_LUT(*in); } return ℑ } Luma_levels* clone(map &_settings) { return new Luma_levels(_settings);}; protected: unsigned char *LUT; }; 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(){ //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","Fadout amount",1.0f,0.0f,1.0f); create_attribute("mode","blend mode for echoes","Blend mode","screen",{"screen","wrap","min","max"}); title="Echo trails"; description="Draw trail frames additively that fade off over time"; NODEID="5b1ab684-2d0b-11e3-8fa2-970be8c360dd"; lastframe=-1; }; Echo_trails(map &settings):Echo_trails() { base_settings(settings); } ~Echo_trails(){ 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? //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=((Time_spec)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>(int)parameters["number"]->value||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) { 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); Image *in2=(((Image_node*)image_inputs[0]->connection)->get_image_output(wanted)); if (in2) apply_LUT(*(in2),*(images[absframe])); else images[absframe]->clear(); } 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; } if (attributes["mode"]->value=="wrap") { image.add_wrap(*temp); } if (attributes["mode"]->value=="min") { image.min(*temp); } if (attributes["mode"]->value=="max") { image.max(*temp); } delete temp; } else { if (attributes["mode"]->value=="screen") { image+=*(images[absframe]); } if (attributes["mode"]->value=="screen") { image=image.add_wrap(*(images[absframe])); } if (attributes["mode"]->value=="min") { image=image.min(*(images[absframe])); } if (attributes["mode"]->value=="max") { image=image.max(*(images[absframe])); } } } } //for (int i=0;iRGBdata[i]=LUT[in->RGBdata[i]]; //} lastframe=thisframe; } } return ℑ } Echo_trails* clone(map &_settings) { return new Echo_trails(_settings);}; protected: int interval,total,lastframe; //number of frames between displayed echoes unordered_map images; }; class RGB_levels: public Image_node { public: RGB_levels(){ create_image_input("image input","Image input"); create_parameter("red_black_in","number","Red input black-point","Red input black-point",0.0f,0.0f,1.0f); create_parameter("red_white_in","number","Red input white-point","Red input white-point",1.0f,0.0f,1.0f); create_parameter("red_gamma","number","Red gamma level","Red gamma",1.0f,0.01f,10.0f); create_parameter("red_black_out","number","Red output black point","Red output black point",0.0f,0.0f,1.0f); create_parameter("red_white_out","number","Red output white point","Red output white point",1.0f,0.0f,1.0f); create_parameter("green_black_in","number","Green input black point","Green input black point",0.0f,0.0f,1.0f); create_parameter("green_white_in","number","Green input white point","Green input white point",1.0f,0.0f,1.0f); create_parameter("green_gamma","number","Green gamma level","Green gamma",1.0f,0.01f,10.0f); create_parameter("green_black_out","number","Green output black point","Green output black point",0.0f,0.0f,1.0f); create_parameter("green_white_out","number","Green output white point","Green output white point",1.0f,0.0f,1.0f); create_parameter("blue_black_in","number","Blue input black point","Blue input black point",0.0f,0.0f,1.0f); create_parameter("blue_white_in","number","Blue input white point","Blue input white point",1.0f,0.0f,1.0f); create_parameter("blue_gamma","number","Blue gamma level","Blue gamma",1.0f,0.01f,10.0f); create_parameter("blue_black_out","number","Blue output black point","Blue output black point",0.0f,0.0f,1.0f); create_parameter("blue_white_out","number","Blue output white point","Blue output white point",1.0f,0.0f,1.0f); title="RGB levels"; description="Remap RGB values of image"; LUT=nullptr; NODEID="68522cba-2d0b-11e3-8767-8f3c605e9bed"; }; RGB_levels(map &settings):RGB_levels() { base_settings(settings); } ~RGB_levels(){ if (LUT) { for (int i=0;i<3;i++) { delete[] LUT[i]; } delete[] LUT; } }; void generate_LUT(){ //can check here if anything has changed if (LUT) { for (int i=0;i<3;i++) { delete[] LUT[i]; } delete[] LUT; } LUT=new unsigned char*[3]; for (int i=0;i<3;i++){ LUT[i]=new unsigned char[256]; } float fltmax=(255.0f/256.0f); for (int i=0;i<256;i++){ LUT[0][i]=(unsigned char)(((\ pow(min(fltmax,max(0.0f,(((((float)i)/256.0f)-parameters["red_black_in"]->value)/(parameters["red_white_in"]->value-parameters["red_black_in"]->value))))\ ,(1.0/parameters["red_gamma"]->value))\ *(parameters["red_white_out"]->value-parameters["red_black_out"]->value))+parameters["red_black_out"]->value)*255.0f); LUT[1][i]=(unsigned char)(((\ pow(min(fltmax,max(0.0f,(((((float)i)/256.0f)-parameters["green_black_in"]->value)/(parameters["green_white_in"]->value-parameters["green_black_in"]->value))))\ ,(1.0/parameters["green_gamma"]->value))\ *(parameters["green_white_out"]->value-parameters["green_black_out"]->value))+parameters["green_black_out"]->value)*255.0f); LUT[2][i]=(unsigned char)(((\ pow(min(fltmax,max(0.0f,(((((float)i)/256.0f)-parameters["blue_black_in"]->value)/(parameters["blue_white_in"]->value-parameters["blue_black_in"]->value))))\ ,(1.0/parameters["blue_gamma"]->value))\ *(parameters["blue_white_out"]->value-parameters["blue_black_out"]->value))+parameters["blue_black_out"]->value)*255.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 for (int i=0;iget(frame); if (in){ generate_LUT(); apply_LUT(*in); } return ℑ } RGB_levels* clone(map &_settings) { return new RGB_levels(_settings);}; protected: unsigned char **LUT; }; } #endif