2 * Cinelerra :: Blue Banana - color modification plugin for Cinelerra-CV
3 * Copyright (C) 2012-2013 Monty <monty@xiph.org>
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26 #include "bcdisplayinfo.h"
27 #include "bcsignals.h"
31 #include "bluebanana.h"
32 #include "bluebananaconfig.h"
33 #include "bluebananawindow.h"
36 #include "loadbalance.h"
37 #include "playback3d.h"
40 #include "workarounds.h"
43 class BluebananaEngine;
44 class BluebananaWindow;
46 REGISTER_PLUGIN(BluebananaMain)
48 BluebananaMain::BluebananaMain(PluginServer *server)
49 : PluginVClient(server)
53 /* be sure a lookup update triggers */
54 lookup_cache.Hsel_lo=-999999;
55 lookup_cache.Ssel_lo=-999999;
56 lookup_cache.Vsel_lo=-999999;
57 lookup_cache.Fsel_lo=-999999;
59 lookup_cache.Sadj_lo=-999999;
60 lookup_cache.Vadj_lo=-999999;
61 lookup_cache.Radj_lo=-999999;
62 lookup_cache.Gadj_lo=-999999;
63 lookup_cache.Badj_lo=-999999;
69 memset(red_histogram,0,sizeof(red_histogram));
70 memset(green_histogram,0,sizeof(green_histogram));
71 memset(blue_histogram,0,sizeof(blue_histogram));
72 memset(hue_histogram,0,sizeof(hue_histogram));
73 memset(sat_histogram,0,sizeof(sat_histogram));
74 memset(value_histogram,0,sizeof(value_histogram));
77 BluebananaMain::~BluebananaMain() {
79 // if ants are running, run one more pane update to hide them (gui
80 // is already marked as closed)
81 //if(server && server->mwindow && ants_counter>0)
82 // server->mwindow->sync_parameters();
87 const char* BluebananaMain::plugin_title() { return N_("Blue Banana"); }
88 int BluebananaMain::is_realtime() { return 1; }
90 NEW_WINDOW_MACRO(BluebananaMain, BluebananaWindow)
91 LOAD_CONFIGURATION_MACRO(BluebananaMain, BluebananaConfig)
93 void BluebananaMain::render_gui(void *data){
95 BluebananaMain *that = (BluebananaMain *)data; // that is server-side
96 BluebananaWindow *window = (BluebananaWindow *)thread->window;
97 window->lock_window("BluebananaMain::render_gui");
99 /* push histogram data to gui */
100 window->update_histograms(that);
102 /* push any colormodel update to gui */
103 if(that->frame && colormodel != that->frame->get_color_model()){
104 colormodel = that->frame->get_color_model();
108 window->unlock_window();
112 void BluebananaMain::update_gui(){
114 BluebananaWindow *window = (BluebananaWindow *)thread->window;
115 window->lock_window("BluebananaMain::update_gui");
116 window->flush_config_change();
117 int reconfigure = load_configuration();
121 window->unlock_window();
125 void BluebananaMain::save_data(KeyFrame *keyframe){
128 // cause data to be stored directly in text
129 output.set_shared_output(keyframe->xbuf);
130 output.tag.set_title("BLUEBANANA");
132 output.tag.set_property("ACTIVE", config.active);
133 output.tag.set_property("OP", config.op);
134 output.tag.set_property("INVERT_SELECTION", config.invert_selection);
135 output.tag.set_property("USE_MASK", config.use_mask);
136 output.tag.set_property("CAPTURE_MASK", config.capture_mask);
138 output.tag.set_property("HUE_ACTIVE", config.Hsel_active);
139 output.tag.set_property("HUE_LO", config.Hsel_lo);
140 output.tag.set_property("HUE_HI", config.Hsel_hi);
141 output.tag.set_property("HUE_OVERLAP", config.Hsel_over);
143 output.tag.set_property("SATURATION_ACTIVE", config.Ssel_active);
144 output.tag.set_property("SATURATION_LO", config.Ssel_lo);
145 output.tag.set_property("SATURATION_HI", config.Ssel_hi);
146 output.tag.set_property("SATURATION_OVERLAP", config.Ssel_over);
148 output.tag.set_property("VALUE_ACTIVE", config.Vsel_active);
149 output.tag.set_property("VALUE_LO", config.Vsel_lo);
150 output.tag.set_property("VALUE_HI", config.Vsel_hi);
151 output.tag.set_property("VALUE_OVERLAP", config.Vsel_over);
153 output.tag.set_property("FILL_ACTIVE", config.Fsel_active);
154 output.tag.set_property("FILL_ERODE", config.Fsel_erode);
155 output.tag.set_property("FILL_LO", config.Fsel_lo);
156 output.tag.set_property("FILL_MID", config.Fsel_mid);
157 output.tag.set_property("FILL_HI", config.Fsel_hi);
158 output.tag.set_property("FILL_FEATHER", config.Fsel_over);
160 output.tag.set_property("HUE_ADJUST_ACTIVE", config.Hadj_active);
161 output.tag.set_property("HUE_ADJUST", config.Hadj_val);
163 output.tag.set_property("SATURATION_ADJUST_ACTIVE", config.Sadj_active);
164 output.tag.set_property("SATURATION_ADJUST_GAMMA", config.Sadj_gamma);
165 output.tag.set_property("SATURATION_ADJUST_LO", config.Sadj_lo);
166 output.tag.set_property("SATURATION_ADJUST_HI", config.Sadj_hi);
168 output.tag.set_property("VALUE_ADJUST_ACTIVE", config.Vadj_active);
169 output.tag.set_property("VALUE_ADJUST_GAMMA", config.Vadj_gamma);
170 output.tag.set_property("VALUE_ADJUST_LO", config.Vadj_lo);
171 output.tag.set_property("VALUE_ADJUST_HI", config.Vadj_hi);
173 output.tag.set_property("RED_ADJUST_ACTIVE", config.Radj_active);
174 output.tag.set_property("RED_ADJUST_GAMMA", config.Radj_gamma);
175 output.tag.set_property("RED_ADJUST_LO", config.Radj_lo);
176 output.tag.set_property("RED_ADJUST_HI", config.Radj_hi);
178 output.tag.set_property("GREEN_ADJUST_ACTIVE", config.Gadj_active);
179 output.tag.set_property("GREEN_ADJUST_GAMMA", config.Gadj_gamma);
180 output.tag.set_property("GREEN_ADJUST_LO", config.Gadj_lo);
181 output.tag.set_property("GREEN_ADJUST_HI", config.Gadj_hi);
183 output.tag.set_property("BLUE_ADJUST_ACTIVE", config.Badj_active);
184 output.tag.set_property("BLUE_ADJUST_GAMMA", config.Badj_gamma);
185 output.tag.set_property("BLUE_ADJUST_LO", config.Badj_lo);
186 output.tag.set_property("BLUE_ADJUST_HI", config.Badj_hi);
188 output.tag.set_property("OPACITY_ADJUST_ACTIVE", config.Oadj_active);
189 output.tag.set_property("OPACITY_ADJUST", config.Oadj_val);
190 output.tag.set_property("ALPHA_ADJUST_ACTIVE", config.Aadj_active);
191 output.tag.set_property("ALPHA_ADJUST", config.Aadj_val);
194 output.append_newline();
196 output.tag.set_title("/BLUEBANANA");
198 output.append_newline();
200 if(keyframe->position==0){
201 /* this will otherwise overwrite the nonauto information as well */
202 output_nonauto(&output);
205 output.terminate_string();
208 // save non-auto data to the default keyframe at zero and does it
209 // without alerting the keyframing mechanism
210 void BluebananaMain::save_nonauto(){
212 KeyFrame *default_keyframe=get_prev_keyframe(0);
213 if(default_keyframe){
218 input.read_from_string(default_keyframe->get_data());
219 output.set_shared_output(default_keyframe->xbuf);
222 result = input.read_tag();
225 !input.tag.title_is("BLUEBANANA_NONAUTO") &&
226 !input.tag.title_is("/BLUEBANANA_NONAUTO")){
227 input.tag.write_tag(&output);
228 output.append_newline();
232 output_nonauto(&output);
236 void BluebananaMain::output_nonauto(FileXML *output){
237 output->tag.set_title("BLUEBANANA_NONAUTO");
238 output->tag.set_property("MARK", config.mark);
239 output->append_tag();
240 output->tag.set_title("/BLUEBANANA_NONAUTO");
241 output->append_tag();
242 output->append_newline();
243 output->terminate_string();
246 void BluebananaMain::load_nonauto(){
247 /* nonauto data stored in the default keyframe at position 0 */
248 KeyFrame *default_keyframe=get_prev_keyframe(0);
249 if(default_keyframe){
252 input.set_shared_input(default_keyframe->xbuf);
255 result = input.read_tag();
257 if(!result && input.tag.title_is("BLUEBANANA_NONAUTO")){
258 config.mark = input.tag.get_property("MARK", config.mark);
265 void BluebananaMain::read_data(KeyFrame *keyframe){
268 { FileXML input; // release xbuf-shared_lock before nonaauto
269 input.set_shared_input(keyframe->xbuf);
272 result = input.read_tag();
274 if(!result && input.tag.title_is("BLUEBANANA")){
275 config.active = input.tag.get_property("ACTIVE", config.active);
276 config.op = input.tag.get_property("OP", config.op);
277 config.invert_selection = input.tag.get_property("INVERT_SELECTION", config.invert_selection);
278 config.use_mask = input.tag.get_property("USE_MASK", config.use_mask);
279 config.capture_mask = input.tag.get_property("CAPTURE_MASK", config.capture_mask);
281 config.Hsel_active = input.tag.get_property("HUE_ACTIVE", config.Hsel_active);
282 config.Hsel_lo = input.tag.get_property("HUE_LO", config.Hsel_lo);
283 config.Hsel_hi = input.tag.get_property("HUE_HI", config.Hsel_hi);
284 config.Hsel_over = input.tag.get_property("HUE_OVERLAP", config.Hsel_over);
286 config.Ssel_active = input.tag.get_property("SATURATION_ACTIVE", config.Ssel_active);
287 config.Ssel_lo = input.tag.get_property("SATURATION_LO", config.Ssel_lo);
288 config.Ssel_hi = input.tag.get_property("SATURATION_HI", config.Ssel_hi);
289 config.Ssel_over = input.tag.get_property("SATURATION_OVERLAP", config.Ssel_over);
291 config.Vsel_active = input.tag.get_property("VALUE_ACTIVE", config.Vsel_active);
292 config.Vsel_lo = input.tag.get_property("VALUE_LO", config.Vsel_lo);
293 config.Vsel_hi = input.tag.get_property("VALUE_HI", config.Vsel_hi);
294 config.Vsel_over = input.tag.get_property("VALUE_OVERLAP", config.Vsel_over);
296 config.Fsel_active = input.tag.get_property("FILL_ACTIVE", config.Fsel_active);
297 config.Fsel_erode = input.tag.get_property("FILL_ERODE", config.Fsel_erode);
298 config.Fsel_lo = input.tag.get_property("FILL_LO", config.Fsel_lo);
299 config.Fsel_mid = input.tag.get_property("FILL_MID", config.Fsel_mid);
300 config.Fsel_hi = input.tag.get_property("FILL_HI", config.Fsel_hi);
301 config.Fsel_over = input.tag.get_property("FILL_FEATHER", config.Fsel_over);
303 config.Hadj_active = input.tag.get_property("HUE_ADJUST_ACTIVE", config.Hadj_active);
304 config.Hadj_val = input.tag.get_property("HUE_ADJUST", config.Hadj_val);
306 config.Sadj_active = input.tag.get_property("SATURATION_ADJUST_ACTIVE", config.Sadj_active);
307 config.Sadj_gamma = input.tag.get_property("SATURATION_ADJUST_GAMMA", config.Sadj_gamma);
308 config.Sadj_lo = input.tag.get_property("SATURATION_ADJUST_LO", config.Sadj_lo);
309 config.Sadj_hi = input.tag.get_property("SATURATION_ADJUST_HI", config.Sadj_hi);
311 config.Vadj_active = input.tag.get_property("VALUE_ADJUST_ACTIVE", config.Vadj_active);
312 config.Vadj_gamma = input.tag.get_property("VALUE_ADJUST_GAMMA", config.Vadj_gamma);
313 config.Vadj_lo = input.tag.get_property("VALUE_ADJUST_LO", config.Vadj_lo);
314 config.Vadj_hi = input.tag.get_property("VALUE_ADJUST_HI", config.Vadj_hi);
316 config.Radj_active = input.tag.get_property("RED_ADJUST_ACTIVE", config.Radj_active);
317 config.Radj_gamma = input.tag.get_property("RED_ADJUST_GAMMA", config.Radj_gamma);
318 config.Radj_lo = input.tag.get_property("RED_ADJUST_LO", config.Radj_lo);
319 config.Radj_hi = input.tag.get_property("RED_ADJUST_HI", config.Radj_hi);
321 config.Gadj_active = input.tag.get_property("GREEN_ADJUST_ACTIVE", config.Gadj_active);
322 config.Gadj_gamma = input.tag.get_property("GREEN_ADJUST_GAMMA", config.Gadj_gamma);
323 config.Gadj_lo = input.tag.get_property("GREEN_ADJUST_LO", config.Gadj_lo);
324 config.Gadj_hi = input.tag.get_property("GREEN_ADJUST_HI", config.Gadj_hi);
326 config.Badj_active = input.tag.get_property("BLUE_ADJUST_ACTIVE", config.Badj_active);
327 config.Badj_gamma = input.tag.get_property("BLUE_ADJUST_GAMMA", config.Badj_gamma);
328 config.Badj_lo = input.tag.get_property("BLUE_ADJUST_LO", config.Badj_lo);
329 config.Badj_hi = input.tag.get_property("BLUE_ADJUST_HI", config.Badj_hi);
331 config.Oadj_active = input.tag.get_property("OPACITY_ADJUST_ACTIVE", config.Oadj_active);
332 config.Oadj_val = input.tag.get_property("OPACITY_ADJUST", config.Oadj_val);
333 config.Aadj_active = input.tag.get_property("ALPHA_ADJUST_ACTIVE", config.Aadj_active);
334 config.Aadj_val = input.tag.get_property("ALPHA_ADJUST", config.Aadj_val);
342 static void select_grow_h(float *hrow, float *vrow, int width){
346 for(i=0;i<width-1;i++){
347 if(hrow[i]<hrow[i+1])hrow[i]=hrow[i+1];
348 if(vrow[i]<hrow[i])vrow[i]=hrow[i];
351 for(i=width-1;i>0;i--){
352 if(hrow[i]<hrow[i-1])hrow[i]=hrow[i-1];
353 if(vrow[i]<hrow[i])vrow[i]=hrow[i];
357 static void select_shrink_h(float *hrow, float *vrow, int width){
361 for(i=0;i<width-1;i++){
362 if(hrow[i]>hrow[i+1])hrow[i]=hrow[i+1];
363 if(vrow[i]>hrow[i])vrow[i]=hrow[i];
366 for(i=width-1;i>0;i--){
367 if(hrow[i]>hrow[i-1])hrow[i]=hrow[i-1];
368 if(vrow[i]>hrow[i])vrow[i]=hrow[i];
372 static void select_grow_v(float *row0, float *row1, int width){
377 if(row0[i]<row1[i])row0[i]=row1[i];
380 static void select_shrink_v(float *row0, float *row1, int width){
383 /* spread out of 0 */
385 if(row0[i]>row1[i])row0[i]=row1[i];
388 static void threaded_horizontal(float *in, float *work,
389 int width, int height,
390 BluebananaEngine *e, int tasks, int passes,
391 void(*func)(float *, float *, int)){
393 /* these are individually fast operations; here we do in fact make
394 cache region collisions as impossible as we can. Live with the
395 overhead of the join. */
397 e->set_task(tasks,"H_even");
400 int row = (j*2)*height/(tasks*2);
401 int end = (j*2+1)*height/(tasks*2);
403 for(i=0;i<passes;i++)
404 func(in+row*width,work+row*width,width);
408 e->set_task(0,"H_odd");
410 int row = (j*2+1)*height/(tasks*2);
411 int end = (j*2+2)*height/(tasks*2);
413 for(i=0;i<passes;i++)
414 func(in+row*width,work+row*width,width);
419 static void threaded_vertical(float *work, float *temp,
420 int width, int height,
421 BluebananaEngine *e, int tasks,
422 void(*func)(float *, float *, int)){
424 /* regions overlap, so this becomes a bit more complex. */
426 /* rather than on-demand task allocation, we use the task engine to
427 grab a slot, then reuse this same slot through several joins */
429 e->set_task(tasks,"up_odd");
430 int region = e->next_task(); /* grab one slot */
431 int start_row = (region*2)*height/(tasks*2);
432 int mid_row = (region*2+1)*height/(tasks*2);
433 int end_row = (region*2+2)*height/(tasks*2);
436 /* spread up, starting at middle row, moving down */
437 /* first task is to save a copy of the un-transformed middle row, as
438 we'll need it for the even pass */
439 memcpy(temp,work+mid_row*width,sizeof(*temp)*width);
442 for(row=mid_row;row<end_row-1;row++)
443 func(work+row*width,work+(row+1)*width,width);
444 if(end_row<height && row<end_row)
445 func(work+row*width,work+(row+1)*width,width);
448 /* even interleave */
449 e->set_task(0,"up_even");
450 for(row=start_row;row<mid_row-1;row++)
451 func(work+row*width,work+(row+1)*width,width);
453 func(work+row*width,temp,width);
456 /* spread down, starting at mid row and moving up */
457 /* once again, grab a temp vector for the second pass overlap */
458 memcpy(temp,work+mid_row*width,sizeof(*temp)*width);
460 /* even interleave */
461 e->set_task(0,"down_even");
462 for(row=mid_row;row>start_row;row--)
463 func(work+row*width,work+(row-1)*width,width);
465 func(work+row*width,work+(row-1)*width,width);
469 e->set_task(0,"down_odd");
470 for(row=end_row-1;row>mid_row+1;row--)
471 func(work+row*width,work+(row-1)*width,width);
473 func(work+row*width,temp,width);
479 static float *fill_one(float *in, float *work,
480 int width, int height,
481 BluebananaEngine *e, // NULL if we're not threading
485 int tasks = e?e->get_total_packages():0;
492 /* multiple memcpys running at once is occasionally a total
493 cache disaster on Sandy Bridge. So only one thread gets to
494 copy, the others wait. */
495 e->set_task(1,"fill_memcpy");
496 while((j = e->next_task())>=0){
497 memcpy(work,in,width*height*sizeof(*work));
501 memcpy(work,in,width*height*sizeof(*work));
507 case 'H': /* grow horizontal */
509 threaded_horizontal(in, work, width, height, e, tasks, 1, select_grow_h);
511 for(j=0;j<height;j++)
512 select_grow_h(in+j*width,work+j*width,width);
516 case 'V': /* grow vertical */
518 threaded_vertical(work, temp, width, height, e, tasks, select_grow_v);
520 for(j=0;j<height-1;j++)
521 select_grow_v(work+j*width,work+(j+1)*width,width);
522 for(j=height-1;j>0;j--)
523 select_grow_v(work+j*width,work+(j-1)*width,width);
527 case 'h': /* shrink horizontal */
529 threaded_horizontal(in, work, width, height, e, tasks, 1, select_shrink_h);
531 for(j=0;j<height;j++)
532 select_shrink_h(in+j*width,work+j*width,width);
536 default: /* shrink vertical */
539 threaded_vertical(work, temp, width, height, e, tasks, select_shrink_v);
541 for(j=0;j<height-1;j++)
542 select_shrink_v(work+j*width,work+(j+1)*width,width);
543 for(j=height-1;j>0;j--)
544 select_shrink_v(work+j*width,work+(j-1)*width,width);
555 static void select_feather_h(float *p, float *dummy, int w){
556 for(int x=0;x<w-1;x++)
557 p[x] = (p[x]+p[x+1])*.5;
558 for(int x=w-1;x>0;x--)
559 p[x] = (p[x]+p[x-1])*.5;
562 static void select_feather_v(float *p0, float *p1, int w){
563 for(int x=0; x<w; x++)
564 p0[x] = (p0[x]+p1[x])*.5;
567 static void feather_one(float *in,
568 int width, int height,
569 BluebananaEngine *e, // NULL if we're not threading
572 int tasks = e?e->get_total_packages():0;
576 threaded_horizontal(in, 0, width, height, e, tasks, n, select_feather_h);
578 threaded_vertical(in, temp, width, height, e, tasks, select_feather_v);
580 for(j=0;j<height;j++)
582 select_feather_h(in+j*width,0,width);
585 for(j=0;j<height-1;j++)
586 select_feather_v(in+j*width,in+(j+1)*width,width);
587 for(j=height-1;j>0;j--)
588 select_feather_v(in+j*width,in+(j-1)*width,width);
593 /* here and not in the engine as the GUI also uses it (without
595 float *BluebananaMain::fill_selection(float *in, float *work,
596 int width, int height,
597 BluebananaEngine *e){
602 C=fill_one(A,B,width,height,e,select_one,select_one_n);
603 C=fill_one(C,(C==A?B:A),width,height,e,select_two,select_two_n);
604 C=fill_one(C,(C==A?B:A),width,height,e,select_three,select_three_n);
606 feather_one(C,width,height,e,config.Fsel_over);
612 int BluebananaMain::process_buffer(VFrame *frame,
613 int64_t start_position,
617 load_configuration();
624 read_frame(frame, 0, start_position, frame_rate, 0);
627 engine = new BluebananaEngine(this, get_project_smp() + 1,
628 get_project_smp() + 1);
630 engine->process_packages(frame);
632 // push final histograms to UI if it's up
634 send_render_gui(this);