4 * Copyright (C) 2008 Adam Williams <broadcast at earthling dot net>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 #include "bcdisplayinfo.h"
24 #include "bcsignals.h"
25 #include "chromakey.h"
32 #include "loadbalance.h"
33 #include "playback3d.h"
35 #include "pluginvclient.h"
43 ChromaKeyConfig::ChromaKeyConfig()
45 reset(RESET_DEFAULT_SETTINGS);
48 void ChromaKeyConfig::reset(int clear)
68 case RESET_THRESHOLD :
71 case RESET_DEFAULT_SETTINGS :
84 void ChromaKeyConfig::copy_from(ChromaKeyConfig &src)
89 threshold = src.threshold;
90 use_value = src.use_value;
94 int ChromaKeyConfig::equivalent(ChromaKeyConfig &src)
96 return (EQUIV(red, src.red) &&
97 EQUIV(green, src.green) &&
98 EQUIV(blue, src.blue) &&
99 EQUIV(threshold, src.threshold) &&
100 EQUIV(slope, src.slope) &&
101 use_value == src.use_value);
104 void ChromaKeyConfig::interpolate(ChromaKeyConfig &prev,
105 ChromaKeyConfig &next,
108 int64_t current_frame)
110 double next_scale = (double)(current_frame - prev_frame) / (next_frame - prev_frame);
111 double prev_scale = (double)(next_frame - current_frame) / (next_frame - prev_frame);
113 this->red = prev.red * prev_scale + next.red * next_scale;
114 this->green = prev.green * prev_scale + next.green * next_scale;
115 this->blue = prev.blue * prev_scale + next.blue * next_scale;
116 this->threshold = prev.threshold * prev_scale + next.threshold * next_scale;
117 this->slope = prev.slope * prev_scale + next.slope * next_scale;
118 this->use_value = prev.use_value;
121 int ChromaKeyConfig::get_color()
123 int red = (int)(CLIP(this->red, 0, 1) * 0xff);
124 int green = (int)(CLIP(this->green, 0, 1) * 0xff);
125 int blue = (int)(CLIP(this->blue, 0, 1) * 0xff);
126 return (red << 16) | (green << 8) | blue;
135 ChromaKeyWindow::ChromaKeyWindow(ChromaKey *plugin)
136 : PluginClientWindow(plugin,
143 this->plugin = plugin;
147 ChromaKeyWindow::~ChromaKeyWindow()
152 void ChromaKeyWindow::create_objects()
154 int xs10 = xS(10), xs20 = xS(20), xs100 = xS(100), xs200 = xS(200);
155 int ys10 = yS(10), ys20 = yS(20), ys30 = yS(30), ys40 = yS(40), ys50 = yS(50);
156 int x = xs10, y = ys10, x2 = xS(80), x3 = xS(180);
157 int clr_x = get_w()-x - xS(22); // note: clrBtn_w = 22
158 int defaultBtn_w = xs100;
161 BC_TitleBar *title_bar;
165 add_subwindow(title_bar = new BC_TitleBar(x, y, get_w()-2*x, xs20, xs10, _("Color")));
167 add_subwindow(color = new ChromaKeyColor(plugin, this, x, y));
168 // Info for Sample rectangle: x_slider w_slider w_sample
170 add_subwindow(sample = new BC_SubWindow(x3 + xs200 - xs100, y, xs100, ys50));
172 add_subwindow(use_colorpicker = new ChromaKeyUseColorPicker(plugin, this, x, y));
174 // Key parameters section
176 add_subwindow(title_bar = new BC_TitleBar(x, y, get_w()-2*x, xs20, xs10, _("Key parameters")));
178 add_subwindow(title = new BC_Title(x, y, _("Threshold:")));
179 threshold_text = new ChromaKeyFText(plugin, this,
180 0, &(plugin->config.threshold), (x + x2), y, MIN_VALUE, MAX_VALUE);
181 threshold_text->create_objects();
182 threshold_slider = new ChromaKeyFSlider(plugin,
183 threshold_text, &(plugin->config.threshold), x3, y, MIN_VALUE, MAX_VALUE, xs200);
184 add_subwindow(threshold_slider);
185 threshold_text->slider = threshold_slider;
186 add_subwindow(threshold_Clr = new ChromaKeyClr(plugin, this, clr_x, y, RESET_THRESHOLD));
189 add_subwindow(title = new BC_Title(x, y, _("Slope:")));
190 slope_text = new ChromaKeyFText(plugin, this,
191 0, &(plugin->config.slope), (x + x2), y, MIN_VALUE, MAX_VALUE);
192 slope_text->create_objects();
193 slope_slider = new ChromaKeyFSlider(plugin,
194 slope_text, &(plugin->config.slope), x3, y, MIN_VALUE, MAX_VALUE, xs200);
195 add_subwindow(slope_slider);
196 slope_text->slider = slope_slider;
197 add_subwindow(slope_Clr = new ChromaKeyClr(plugin, this, clr_x, y, RESET_SLOPE));
200 add_subwindow(use_value = new ChromaKeyUseValue(plugin, x, y));
204 add_subwindow(bar = new BC_Bar(x, y, get_w()-2*x));
206 add_subwindow(reset = new ChromaKeyReset(plugin, this, x, y));
207 add_subwindow(default_settings = new ChromaKeyDefaultSettings(plugin, this,
208 (get_w() - xs10 - defaultBtn_w), y, defaultBtn_w));
210 color_thread = new ChromaKeyColorThread(plugin, this);
218 void ChromaKeyWindow::update_sample()
220 sample->set_color(plugin->config.get_color());
225 sample->set_color(BLACK);
226 sample->draw_rectangle(0,
233 void ChromaKeyWindow::done_event(int result)
235 color_thread->close_window();
246 ChromaKeyColor::ChromaKeyColor(ChromaKey *plugin,
247 ChromaKeyWindow *gui,
250 : BC_GenericButton(x,
254 this->plugin = plugin;
257 int ChromaKeyColor::handle_event()
259 gui->color_thread->start_window(
260 plugin->config.get_color(),
267 ChromaKeyFText::ChromaKeyFText(ChromaKey *plugin, ChromaKeyWindow *gui,
268 ChromaKeyFSlider *slider, float *output, int x, int y, float min, float max)
269 : BC_TumbleTextBox(gui, *output,
270 min, max, x, y, xS(60), 2)
272 this->plugin = plugin;
274 this->output = output;
275 this->slider = slider;
281 ChromaKeyFText::~ChromaKeyFText()
285 int ChromaKeyFText::handle_event()
287 *output = atof(get_text());
288 if(*output > max) *output = max;
289 else if(*output < min) *output = min;
290 slider->update(*output);
291 plugin->send_configure_change();
295 ChromaKeyFSlider::ChromaKeyFSlider(ChromaKey *plugin,
296 ChromaKeyFText *text, float *output, int x, int y,
297 float min, float max, int w)
298 : BC_FSlider(x, y, 0, w, w, min, max, *output)
300 this->plugin = plugin;
301 this->output = output;
303 set_precision (0.01);
304 enable_show_value(0); // Hide caption
307 ChromaKeyFSlider::~ChromaKeyFSlider()
311 int ChromaKeyFSlider::handle_event()
313 *output = get_value();
314 text->update(*output);
315 plugin->send_configure_change();
319 ChromaKeyClr::ChromaKeyClr(ChromaKey *plugin, ChromaKeyWindow *gui, int x, int y, int clear)
320 : BC_Button(x, y, plugin->get_theme()->get_image_set("reset_button"))
322 this->plugin = plugin;
327 ChromaKeyClr::~ChromaKeyClr()
331 int ChromaKeyClr::handle_event()
333 plugin->config.reset(clear);
334 gui->update_gui(clear);
335 plugin->send_configure_change();
341 ChromaKeyUseValue::ChromaKeyUseValue(ChromaKey *plugin, int x, int y)
342 : BC_CheckBox(x, y, plugin->config.use_value, _("Use value"))
344 this->plugin = plugin;
346 int ChromaKeyUseValue::handle_event()
348 plugin->config.use_value = get_value();
349 plugin->send_configure_change();
353 ChromaKeyReset::ChromaKeyReset(ChromaKey *plugin, ChromaKeyWindow *gui, int x, int y)
354 : BC_GenericButton(x, y, _("Reset"))
356 this->plugin = plugin;
360 int ChromaKeyReset::handle_event()
362 plugin->config.reset(RESET_ALL);
363 gui->update_gui(RESET_ALL);
364 plugin->send_configure_change();
368 ChromaKeyDefaultSettings::ChromaKeyDefaultSettings(ChromaKey *plugin, ChromaKeyWindow *gui,
370 : BC_GenericButton(x, y, w, _("Default"))
372 this->plugin = plugin;
375 ChromaKeyDefaultSettings::~ChromaKeyDefaultSettings()
378 int ChromaKeyDefaultSettings::handle_event()
380 plugin->config.reset(RESET_DEFAULT_SETTINGS);
381 gui->update_gui(RESET_DEFAULT_SETTINGS);
382 plugin->send_configure_change();
386 ChromaKeyUseColorPicker::ChromaKeyUseColorPicker(ChromaKey *plugin,
387 ChromaKeyWindow *gui,
390 : BC_GenericButton(x, y, _("Use color picker"))
392 this->plugin = plugin;
396 int ChromaKeyUseColorPicker::handle_event()
398 plugin->config.red = plugin->get_red();
399 plugin->config.green = plugin->get_green();
400 plugin->config.blue = plugin->get_blue();
401 gui->update_sample();
402 plugin->send_configure_change();
409 ChromaKeyColorThread::ChromaKeyColorThread(ChromaKey *plugin, ChromaKeyWindow *gui)
410 : ColorPicker(1, _("Inner color"))
412 this->plugin = plugin;
416 int ChromaKeyColorThread::handle_new_color(int output, int alpha)
418 plugin->config.red = (float)(output & 0xff0000) / 0xff0000;
419 plugin->config.green = (float)(output & 0xff00) / 0xff00;
420 plugin->config.blue = (float)(output & 0xff) / 0xff;
421 gui->lock_window("ChromaKeyColorThread::handle_new_color");
422 gui->update_sample();
423 gui->unlock_window();
424 plugin->send_configure_change();
437 ChromaKeyServer::ChromaKeyServer(ChromaKey *plugin)
438 : LoadServer(plugin->PluginClient::smp + 1, plugin->PluginClient::smp + 1)
440 this->plugin = plugin;
442 void ChromaKeyServer::init_packages()
444 for(int i = 0; i < get_total_packages(); i++)
446 ChromaKeyPackage *pkg = (ChromaKeyPackage*)get_package(i);
447 pkg->y1 = plugin->input->get_h() * i / get_total_packages();
448 pkg->y2 = plugin->input->get_h() * (i + 1) / get_total_packages();
452 LoadClient* ChromaKeyServer::new_client()
454 return new ChromaKeyUnit(plugin, this);
456 LoadPackage* ChromaKeyServer::new_package()
458 return new ChromaKeyPackage;
463 ChromaKeyPackage::ChromaKeyPackage()
468 ChromaKeyUnit::ChromaKeyUnit(ChromaKey *plugin, ChromaKeyServer *server)
471 this->plugin = plugin;
475 void ChromaKeyUnit::process_package(LoadPackage *package)
477 ChromaKeyPackage *pkg = (ChromaKeyPackage*)package;
479 int w = plugin->input->get_w();
482 HSV::rgb_to_hsv(plugin->config.red,
483 plugin->config.green,
488 //float min_hue = h - plugin->config.threshold * 360 / 100;
489 //float max_hue = h + plugin->config.threshold * 360 / 100;
492 #define RGB_TO_VALUE(r, g, b) YUV::yuv.rgb_to_y_f((r),(g),(b))
494 #define OUTER_VARIABLES(plugin) \
495 float value = RGB_TO_VALUE(plugin->config.red, \
496 plugin->config.green, \
497 plugin->config.blue); \
498 float threshold = plugin->config.threshold / 100; \
499 float min_v = value - threshold; \
500 float max_v = value + threshold; \
501 float r_key = plugin->config.red; \
502 float g_key = plugin->config.green; \
503 float b_key = plugin->config.blue; \
504 int y_key, u_key, v_key; \
505 YUV::yuv.rgb_to_yuv_8( \
506 (int)(r_key * 0xff), (int)(g_key * 0xff), (int)(b_key * 0xff), \
507 y_key, u_key, v_key); \
508 float run = plugin->config.slope / 100; \
509 float threshold_run = threshold + run;
511 OUTER_VARIABLES(plugin)
515 #define CHROMAKEY(type, components, max, use_yuv) \
517 for(int i = pkg->y1; i < pkg->y2; i++) \
519 type *row = (type*)plugin->input->get_rows()[i]; \
521 for(int j = 0; j < w; j++) \
525 /* Test for value in range */ \
526 if(plugin->config.use_value) \
528 float current_value; \
531 float r = (float)row[0] / max; \
536 float r = (float)row[0] / max; \
537 float g = (float)row[1] / max; \
538 float b = (float)row[2] / max; \
539 current_value = RGB_TO_VALUE(r, g, b); \
542 /* Full transparency if in range */ \
543 if(current_value >= min_v && current_value < max_v) \
548 /* Phased out if below or above range */ \
549 if(current_value < min_v) \
551 if(min_v - current_value < run) \
552 a = (min_v - current_value) / run; \
555 if(current_value - max_v < run) \
556 a = (current_value - max_v) / run; \
559 /* Use color cube */ \
567 difference = sqrt(SQR(y - y_key) + \
569 SQR(v - v_key)) / max; \
573 float r = (float)row[0] / max; \
574 float g = (float)row[1] / max; \
575 float b = (float)row[2] / max; \
576 difference = sqrt(SQR(r - r_key) + \
580 if(difference < threshold) \
585 if(difference < threshold_run) \
587 a = (difference - threshold) / run; \
592 /* Multiply alpha and put back in frame */ \
593 if(components == 4) \
595 row[3] = MIN((type)(a * max), row[3]); \
600 row[0] = (type)(a * row[0]); \
601 row[1] = (type)(a * (row[1] - (max / 2 + 1)) + max / 2 + 1); \
602 row[2] = (type)(a * (row[2] - (max / 2 + 1)) + max / 2 + 1); \
606 row[0] = (type)(a * row[0]); \
607 row[1] = (type)(a * row[1]); \
608 row[2] = (type)(a * row[2]); \
619 switch(plugin->input->get_color_model())
622 CHROMAKEY(float, 3, 1.0, 0);
625 CHROMAKEY(float, 4, 1.0, 0);
628 CHROMAKEY(unsigned char, 3, 0xff, 0);
631 CHROMAKEY(unsigned char, 4, 0xff, 0);
634 CHROMAKEY(unsigned char, 3, 0xff, 1);
637 CHROMAKEY(unsigned char, 4, 0xff, 1);
640 CHROMAKEY(uint16_t, 3, 0xffff, 1);
642 case BC_YUVA16161616:
643 CHROMAKEY(uint16_t, 4, 0xffff, 1);
653 REGISTER_PLUGIN(ChromaKey)
657 ChromaKey::ChromaKey(PluginServer *server)
658 : PluginVClient(server)
664 ChromaKey::~ChromaKey()
671 int ChromaKey::process_buffer(VFrame *frame,
672 int64_t start_position,
677 load_configuration();
679 this->output = frame;
687 if(EQUIV(config.threshold, 0))
693 if(get_use_opengl()) return run_opengl();
695 if(!engine) engine = new ChromaKeyServer(this);
696 engine->process_packages();
703 const char* ChromaKey::plugin_title() { return N_("Chroma key"); }
704 int ChromaKey::is_realtime() { return 1; }
706 NEW_WINDOW_MACRO(ChromaKey, ChromaKeyWindow)
708 LOAD_CONFIGURATION_MACRO(ChromaKey, ChromaKeyConfig)
711 void ChromaKey::save_data(KeyFrame *keyframe)
714 output.set_shared_output(keyframe->xbuf);
715 output.tag.set_title("CHROMAKEY");
716 output.tag.set_property("RED", config.red);
717 output.tag.set_property("GREEN", config.green);
718 output.tag.set_property("BLUE", config.blue);
719 output.tag.set_property("THRESHOLD", config.threshold);
720 output.tag.set_property("SLOPE", config.slope);
721 output.tag.set_property("USE_VALUE", config.use_value);
723 output.tag.set_title("/CHROMAKEY");
725 output.append_newline();
726 output.terminate_string();
729 void ChromaKey::read_data(KeyFrame *keyframe)
733 input.set_shared_input(keyframe->xbuf);
735 while(!input.read_tag())
737 if(input.tag.title_is("CHROMAKEY"))
739 config.red = input.tag.get_property("RED", config.red);
740 config.green = input.tag.get_property("GREEN", config.green);
741 config.blue = input.tag.get_property("BLUE", config.blue);
742 config.threshold = input.tag.get_property("THRESHOLD", config.threshold);
743 config.slope = input.tag.get_property("SLOPE", config.slope);
744 config.use_value = input.tag.get_property("USE_VALUE", config.use_value);
751 void ChromaKey::update_gui()
755 load_configuration();
756 thread->window->lock_window();
757 ((ChromaKeyWindow *)(thread->window))->update_gui(RESET_ALL);
758 thread->window->unlock_window();
762 void ChromaKeyWindow::update_gui(int clear)
764 ChromaKeyConfig &config = plugin->config;
770 slope_text->update(config.slope);
771 slope_slider->update(config.slope);
773 case RESET_THRESHOLD :
774 threshold_text->update(config.threshold);
775 threshold_slider->update(config.threshold);
778 case RESET_DEFAULT_SETTINGS :
781 slope_text->update(config.slope);
782 slope_slider->update(config.slope);
783 threshold_text->update(config.threshold);
784 threshold_slider->update(config.threshold);
785 use_value->update(config.use_value);
790 int ChromaKey::handle_opengl()
793 OUTER_VARIABLES(this)
797 static const char *uniform_frag =
798 "uniform sampler2D tex;\n"
799 "uniform float min_v;\n"
800 "uniform float max_v;\n"
801 "uniform float run;\n"
802 "uniform float threshold;\n"
803 "uniform float threshold_run;\n"
804 "uniform vec3 key;\n";
806 static const char *get_yuvvalue_frag =
807 "float get_value(vec4 color)\n"
809 " return abs(color.r);\n"
812 static const char *get_rgbvalue_frag =
813 "uniform vec3 rgb_to_y_vector;\n"
814 "uniform float yminf;\n"
815 "float get_value(vec4 color)\n"
817 " return dot(color.rgb, rgb_to_y_vector) + yminf;\n"
820 static const char *value_frag =
823 " vec4 color = texture2D(tex, gl_TexCoord[0].st);\n"
824 " float value = get_value(color);\n"
825 " float alpha = 1.0;\n"
827 " if(value >= min_v && value < max_v)\n"
830 " if(value < min_v)\n"
832 " if(min_v - value < run)\n"
833 " alpha = (min_v - value) / run;\n"
836 " if(value - max_v < run)\n"
837 " alpha = (value - max_v) / run;\n"
839 " gl_FragColor = vec4(color.rgb, alpha);\n"
842 static const char *cube_frag =
845 " vec4 color = texture2D(tex, gl_TexCoord[0].st);\n"
846 " float difference = length(color.rgb - key);\n"
847 " float alpha = 1.0;\n"
848 " if(difference < threshold)\n"
851 " if(difference < threshold_run)\n"
852 " alpha = (difference - threshold) / run;\n"
853 " gl_FragColor = vec4(color.rgb, min(color.a, alpha));\n"
858 get_output()->to_texture();
859 get_output()->enable_opengl();
860 get_output()->init_screen();
862 const char *shader_stack[16];
863 memset(shader_stack,0, sizeof(shader_stack));
864 int current_shader = 0;
865 shader_stack[current_shader++] = uniform_frag;
867 switch(get_output()->get_color_model()) {
870 if( config.use_value ) {
871 shader_stack[current_shader++] = get_yuvvalue_frag;
872 shader_stack[current_shader++] = value_frag;
875 shader_stack[current_shader++] = cube_frag;
880 if(config.use_value) {
881 shader_stack[current_shader++] = get_rgbvalue_frag;
882 shader_stack[current_shader++] = value_frag;
885 shader_stack[current_shader++] = cube_frag;
891 shader_stack[current_shader] = 0;
892 unsigned int shader = VFrame::make_shader(shader_stack);
894 get_output()->bind_texture(0);
895 glUseProgram(shader);
896 glUniform1i(glGetUniformLocation(shader, "tex"), 0);
897 glUniform1f(glGetUniformLocation(shader, "min_v"), min_v);
898 glUniform1f(glGetUniformLocation(shader, "max_v"), max_v);
899 glUniform1f(glGetUniformLocation(shader, "run"), run);
900 glUniform1f(glGetUniformLocation(shader, "threshold"), threshold);
901 glUniform1f(glGetUniformLocation(shader, "threshold_run"), threshold_run);
902 if(get_output()->get_color_model() != BC_YUV888 &&
903 get_output()->get_color_model() != BC_YUVA8888)
904 glUniform3f(glGetUniformLocation(shader, "key"),
905 r_key, g_key, b_key);
907 glUniform3f(glGetUniformLocation(shader, "key"),
908 (float)y_key / 0xff, (float)u_key / 0xff, (float)v_key / 0xff);
910 BC_GL_RGB_TO_Y(shader);
914 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
915 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
917 if(BC_CModels::components(get_output()->get_color_model()) == 3)
920 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
921 get_output()->clear_pbuffer();
925 get_output()->draw_texture();
928 get_output()->set_opengl_state(VFrame::SCREEN);
929 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
930 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);