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()
48 void ChromaKeyConfig::reset()
60 void ChromaKeyConfig::copy_from(ChromaKeyConfig &src)
65 threshold = src.threshold;
66 use_value = src.use_value;
70 int ChromaKeyConfig::equivalent(ChromaKeyConfig &src)
72 return (EQUIV(red, src.red) &&
73 EQUIV(green, src.green) &&
74 EQUIV(blue, src.blue) &&
75 EQUIV(threshold, src.threshold) &&
76 EQUIV(slope, src.slope) &&
77 use_value == src.use_value);
80 void ChromaKeyConfig::interpolate(ChromaKeyConfig &prev,
81 ChromaKeyConfig &next,
84 int64_t current_frame)
86 double next_scale = (double)(current_frame - prev_frame) / (next_frame - prev_frame);
87 double prev_scale = (double)(next_frame - current_frame) / (next_frame - prev_frame);
89 this->red = prev.red * prev_scale + next.red * next_scale;
90 this->green = prev.green * prev_scale + next.green * next_scale;
91 this->blue = prev.blue * prev_scale + next.blue * next_scale;
92 this->threshold = prev.threshold * prev_scale + next.threshold * next_scale;
93 this->slope = prev.slope * prev_scale + next.slope * next_scale;
94 this->use_value = prev.use_value;
97 int ChromaKeyConfig::get_color()
99 int red = (int)(CLIP(this->red, 0, 1) * 0xff);
100 int green = (int)(CLIP(this->green, 0, 1) * 0xff);
101 int blue = (int)(CLIP(this->blue, 0, 1) * 0xff);
102 return (red << 16) | (green << 8) | blue;
111 ChromaKeyWindow::ChromaKeyWindow(ChromaKey *plugin)
112 : PluginClientWindow(plugin,
119 this->plugin = plugin;
123 ChromaKeyWindow::~ChromaKeyWindow()
128 void ChromaKeyWindow::create_objects()
130 int xs10 = xS(10), xs100 = xS(100);
131 int ys10 = yS(10), ys30 = yS(30), ys50 = yS(50);
132 int x = xs10, y = ys10, x1 = xS(100);
135 add_subwindow(title = new BC_Title(x, y, _("Color:")));
136 x += title->get_w() + xs10;
137 add_subwindow(color = new ChromaKeyColor(plugin, this, x, y));
138 x += color->get_w() + xs10;
139 add_subwindow(sample = new BC_SubWindow(x, y, xs100, ys50));
140 y += sample->get_h() + xs10;
143 add_subwindow(new BC_Title(x, y, _("Slope:")));
144 add_subwindow(slope = new ChromaKeySlope(plugin, x1, y));
147 add_subwindow(new BC_Title(x, y, _("Threshold:")));
148 add_subwindow(threshold = new ChromaKeyThreshold(plugin, x1, y));
152 add_subwindow(use_value = new ChromaKeyUseValue(plugin, x1, y));
155 add_subwindow(use_colorpicker = new ChromaKeyUseColorPicker(plugin, this, x1, y));
157 y += use_colorpicker->get_h() + xs10;
158 add_subwindow(new ChromaKeyReset(plugin, this, x, y));
160 color_thread = new ChromaKeyColorThread(plugin, this);
167 void ChromaKeyWindow::update_sample()
169 sample->set_color(plugin->config.get_color());
174 sample->set_color(BLACK);
175 sample->draw_rectangle(0,
182 void ChromaKeyWindow::done_event(int result)
184 color_thread->close_window();
195 ChromaKeyColor::ChromaKeyColor(ChromaKey *plugin,
196 ChromaKeyWindow *gui,
199 : BC_GenericButton(x,
203 this->plugin = plugin;
206 int ChromaKeyColor::handle_event()
208 gui->color_thread->start_window(
209 plugin->config.get_color(),
217 ChromaKeyThreshold::ChromaKeyThreshold(ChromaKey *plugin, int x, int y)
225 plugin->config.threshold)
227 this->plugin = plugin;
230 int ChromaKeyThreshold::handle_event()
232 plugin->config.threshold = get_value();
233 plugin->send_configure_change();
238 ChromaKeySlope::ChromaKeySlope(ChromaKey *plugin, int x, int y)
246 plugin->config.slope)
248 this->plugin = plugin;
252 int ChromaKeySlope::handle_event()
254 plugin->config.slope = get_value();
255 plugin->send_configure_change();
259 ChromaKeyUseValue::ChromaKeyUseValue(ChromaKey *plugin, int x, int y)
260 : BC_CheckBox(x, y, plugin->config.use_value, _("Use value"))
262 this->plugin = plugin;
264 int ChromaKeyUseValue::handle_event()
266 plugin->config.use_value = get_value();
267 plugin->send_configure_change();
271 ChromaKeyReset::ChromaKeyReset(ChromaKey *plugin, ChromaKeyWindow *gui, int x, int y)
272 : BC_GenericButton(x, y, _("Reset"))
274 this->plugin = plugin;
278 int ChromaKeyReset::handle_event()
280 plugin->config.reset();
282 plugin->send_configure_change();
286 ChromaKeyUseColorPicker::ChromaKeyUseColorPicker(ChromaKey *plugin,
287 ChromaKeyWindow *gui,
290 : BC_GenericButton(x, y, _("Use color picker"))
292 this->plugin = plugin;
296 int ChromaKeyUseColorPicker::handle_event()
298 plugin->config.red = plugin->get_red();
299 plugin->config.green = plugin->get_green();
300 plugin->config.blue = plugin->get_blue();
301 gui->update_sample();
302 plugin->send_configure_change();
309 ChromaKeyColorThread::ChromaKeyColorThread(ChromaKey *plugin, ChromaKeyWindow *gui)
310 : ColorPicker(1, _("Inner color"))
312 this->plugin = plugin;
316 int ChromaKeyColorThread::handle_new_color(int output, int alpha)
318 plugin->config.red = (float)(output & 0xff0000) / 0xff0000;
319 plugin->config.green = (float)(output & 0xff00) / 0xff00;
320 plugin->config.blue = (float)(output & 0xff) / 0xff;
321 gui->lock_window("ChromaKeyColorThread::handle_new_color");
322 gui->update_sample();
323 gui->unlock_window();
324 plugin->send_configure_change();
337 ChromaKeyServer::ChromaKeyServer(ChromaKey *plugin)
338 : LoadServer(plugin->PluginClient::smp + 1, plugin->PluginClient::smp + 1)
340 this->plugin = plugin;
342 void ChromaKeyServer::init_packages()
344 for(int i = 0; i < get_total_packages(); i++)
346 ChromaKeyPackage *pkg = (ChromaKeyPackage*)get_package(i);
347 pkg->y1 = plugin->input->get_h() * i / get_total_packages();
348 pkg->y2 = plugin->input->get_h() * (i + 1) / get_total_packages();
352 LoadClient* ChromaKeyServer::new_client()
354 return new ChromaKeyUnit(plugin, this);
356 LoadPackage* ChromaKeyServer::new_package()
358 return new ChromaKeyPackage;
363 ChromaKeyPackage::ChromaKeyPackage()
368 ChromaKeyUnit::ChromaKeyUnit(ChromaKey *plugin, ChromaKeyServer *server)
371 this->plugin = plugin;
375 void ChromaKeyUnit::process_package(LoadPackage *package)
377 ChromaKeyPackage *pkg = (ChromaKeyPackage*)package;
379 int w = plugin->input->get_w();
382 HSV::rgb_to_hsv(plugin->config.red,
383 plugin->config.green,
388 //float min_hue = h - plugin->config.threshold * 360 / 100;
389 //float max_hue = h + plugin->config.threshold * 360 / 100;
392 #define RGB_TO_VALUE(r, g, b) YUV::yuv.rgb_to_y_f((r),(g),(b))
394 #define OUTER_VARIABLES(plugin) \
395 float value = RGB_TO_VALUE(plugin->config.red, \
396 plugin->config.green, \
397 plugin->config.blue); \
398 float threshold = plugin->config.threshold / 100; \
399 float min_v = value - threshold; \
400 float max_v = value + threshold; \
401 float r_key = plugin->config.red; \
402 float g_key = plugin->config.green; \
403 float b_key = plugin->config.blue; \
404 int y_key, u_key, v_key; \
405 YUV::yuv.rgb_to_yuv_8( \
406 (int)(r_key * 0xff), (int)(g_key * 0xff), (int)(b_key * 0xff), \
407 y_key, u_key, v_key); \
408 float run = plugin->config.slope / 100; \
409 float threshold_run = threshold + run;
411 OUTER_VARIABLES(plugin)
415 #define CHROMAKEY(type, components, max, use_yuv) \
417 for(int i = pkg->y1; i < pkg->y2; i++) \
419 type *row = (type*)plugin->input->get_rows()[i]; \
421 for(int j = 0; j < w; j++) \
425 /* Test for value in range */ \
426 if(plugin->config.use_value) \
428 float current_value; \
431 float r = (float)row[0] / max; \
436 float r = (float)row[0] / max; \
437 float g = (float)row[1] / max; \
438 float b = (float)row[2] / max; \
439 current_value = RGB_TO_VALUE(r, g, b); \
442 /* Full transparency if in range */ \
443 if(current_value >= min_v && current_value < max_v) \
448 /* Phased out if below or above range */ \
449 if(current_value < min_v) \
451 if(min_v - current_value < run) \
452 a = (min_v - current_value) / run; \
455 if(current_value - max_v < run) \
456 a = (current_value - max_v) / run; \
459 /* Use color cube */ \
467 difference = sqrt(SQR(y - y_key) + \
469 SQR(v - v_key)) / max; \
473 float r = (float)row[0] / max; \
474 float g = (float)row[1] / max; \
475 float b = (float)row[2] / max; \
476 difference = sqrt(SQR(r - r_key) + \
480 if(difference < threshold) \
485 if(difference < threshold_run) \
487 a = (difference - threshold) / run; \
492 /* Multiply alpha and put back in frame */ \
493 if(components == 4) \
495 row[3] = MIN((type)(a * max), row[3]); \
500 row[0] = (type)(a * row[0]); \
501 row[1] = (type)(a * (row[1] - (max / 2 + 1)) + max / 2 + 1); \
502 row[2] = (type)(a * (row[2] - (max / 2 + 1)) + max / 2 + 1); \
506 row[0] = (type)(a * row[0]); \
507 row[1] = (type)(a * row[1]); \
508 row[2] = (type)(a * row[2]); \
519 switch(plugin->input->get_color_model())
522 CHROMAKEY(float, 3, 1.0, 0);
525 CHROMAKEY(float, 4, 1.0, 0);
528 CHROMAKEY(unsigned char, 3, 0xff, 0);
531 CHROMAKEY(unsigned char, 4, 0xff, 0);
534 CHROMAKEY(unsigned char, 3, 0xff, 1);
537 CHROMAKEY(unsigned char, 4, 0xff, 1);
540 CHROMAKEY(uint16_t, 3, 0xffff, 1);
542 case BC_YUVA16161616:
543 CHROMAKEY(uint16_t, 4, 0xffff, 1);
553 REGISTER_PLUGIN(ChromaKey)
557 ChromaKey::ChromaKey(PluginServer *server)
558 : PluginVClient(server)
564 ChromaKey::~ChromaKey()
571 int ChromaKey::process_buffer(VFrame *frame,
572 int64_t start_position,
577 load_configuration();
579 this->output = frame;
587 if(EQUIV(config.threshold, 0))
593 if(get_use_opengl()) return run_opengl();
595 if(!engine) engine = new ChromaKeyServer(this);
596 engine->process_packages();
603 const char* ChromaKey::plugin_title() { return N_("Chroma key"); }
604 int ChromaKey::is_realtime() { return 1; }
606 NEW_WINDOW_MACRO(ChromaKey, ChromaKeyWindow)
608 LOAD_CONFIGURATION_MACRO(ChromaKey, ChromaKeyConfig)
611 void ChromaKey::save_data(KeyFrame *keyframe)
614 output.set_shared_output(keyframe->xbuf);
615 output.tag.set_title("CHROMAKEY");
616 output.tag.set_property("RED", config.red);
617 output.tag.set_property("GREEN", config.green);
618 output.tag.set_property("BLUE", config.blue);
619 output.tag.set_property("THRESHOLD", config.threshold);
620 output.tag.set_property("SLOPE", config.slope);
621 output.tag.set_property("USE_VALUE", config.use_value);
623 output.tag.set_title("/CHROMAKEY");
625 output.append_newline();
626 output.terminate_string();
629 void ChromaKey::read_data(KeyFrame *keyframe)
633 input.set_shared_input(keyframe->xbuf);
635 while(!input.read_tag())
637 if(input.tag.title_is("CHROMAKEY"))
639 config.red = input.tag.get_property("RED", config.red);
640 config.green = input.tag.get_property("GREEN", config.green);
641 config.blue = input.tag.get_property("BLUE", config.blue);
642 config.threshold = input.tag.get_property("THRESHOLD", config.threshold);
643 config.slope = input.tag.get_property("SLOPE", config.slope);
644 config.use_value = input.tag.get_property("USE_VALUE", config.use_value);
651 void ChromaKey::update_gui()
655 load_configuration();
656 thread->window->lock_window();
657 ((ChromaKeyWindow *)(thread->window))->update_gui();
658 thread->window->unlock_window();
662 void ChromaKeyWindow::update_gui()
664 ChromaKeyConfig &config = plugin->config;
665 threshold->update(config.threshold);
666 slope->update(config.slope);
667 use_value->update(config.use_value);
671 int ChromaKey::handle_opengl()
674 OUTER_VARIABLES(this)
678 static const char *uniform_frag =
679 "uniform sampler2D tex;\n"
680 "uniform float min_v;\n"
681 "uniform float max_v;\n"
682 "uniform float run;\n"
683 "uniform float threshold;\n"
684 "uniform float threshold_run;\n"
685 "uniform vec3 key;\n";
687 static const char *get_yuvvalue_frag =
688 "float get_value(vec4 color)\n"
690 " return abs(color.r);\n"
693 static const char *get_rgbvalue_frag =
694 "uniform vec3 rgb_to_y_vector;\n"
695 "uniform float yminf;\n"
696 "float get_value(vec4 color)\n"
698 " return dot(color.rgb, rgb_to_y_vector) + yminf;\n"
701 static const char *value_frag =
704 " vec4 color = texture2D(tex, gl_TexCoord[0].st);\n"
705 " float value = get_value(color);\n"
706 " float alpha = 1.0;\n"
708 " if(value >= min_v && value < max_v)\n"
711 " if(value < min_v)\n"
713 " if(min_v - value < run)\n"
714 " alpha = (min_v - value) / run;\n"
717 " if(value - max_v < run)\n"
718 " alpha = (value - max_v) / run;\n"
720 " gl_FragColor = vec4(color.rgb, alpha);\n"
723 static const char *cube_frag =
726 " vec4 color = texture2D(tex, gl_TexCoord[0].st);\n"
727 " float difference = length(color.rgb - key);\n"
728 " float alpha = 1.0;\n"
729 " if(difference < threshold)\n"
732 " if(difference < threshold_run)\n"
733 " alpha = (difference - threshold) / run;\n"
734 " gl_FragColor = vec4(color.rgb, min(color.a, alpha));\n"
739 get_output()->to_texture();
740 get_output()->enable_opengl();
741 get_output()->init_screen();
743 const char *shader_stack[16];
744 memset(shader_stack,0, sizeof(shader_stack));
745 int current_shader = 0;
746 shader_stack[current_shader++] = uniform_frag;
748 switch(get_output()->get_color_model()) {
751 if( config.use_value ) {
752 shader_stack[current_shader++] = get_yuvvalue_frag;
753 shader_stack[current_shader++] = value_frag;
756 shader_stack[current_shader++] = cube_frag;
761 if(config.use_value) {
762 shader_stack[current_shader++] = get_rgbvalue_frag;
763 shader_stack[current_shader++] = value_frag;
766 shader_stack[current_shader++] = cube_frag;
772 shader_stack[current_shader] = 0;
773 unsigned int shader = VFrame::make_shader(shader_stack);
775 get_output()->bind_texture(0);
776 glUseProgram(shader);
777 glUniform1i(glGetUniformLocation(shader, "tex"), 0);
778 glUniform1f(glGetUniformLocation(shader, "min_v"), min_v);
779 glUniform1f(glGetUniformLocation(shader, "max_v"), max_v);
780 glUniform1f(glGetUniformLocation(shader, "run"), run);
781 glUniform1f(glGetUniformLocation(shader, "threshold"), threshold);
782 glUniform1f(glGetUniformLocation(shader, "threshold_run"), threshold_run);
783 if(get_output()->get_color_model() != BC_YUV888 &&
784 get_output()->get_color_model() != BC_YUVA8888)
785 glUniform3f(glGetUniformLocation(shader, "key"),
786 r_key, g_key, b_key);
788 glUniform3f(glGetUniformLocation(shader, "key"),
789 (float)y_key / 0xff, (float)u_key / 0xff, (float)v_key / 0xff);
791 BC_GL_RGB_TO_Y(shader);
795 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
796 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
798 if(BC_CModels::components(get_output()->get_color_model()) == 3)
801 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
802 get_output()->clear_pbuffer();
806 get_output()->draw_texture();
809 get_output()->set_opengl_state(VFrame::SCREEN);
810 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
811 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);