/* * CINELERRA * Copyright (C) 2008 Adam Williams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #ifndef DIFFKEY_H #define DIFFKEY_H #include "bccolors.h" #include "bcdisplayinfo.h" #include "clip.h" #include "bchash.h" #include "filexml.h" #include "guicast.h" #include "language.h" #include "loadbalance.h" #include "bccolors.h" #include "pluginvclient.h" #include "playback3d.h" #include class DiffKeyGUI; class DiffKey; class DiffKeyConfig { public: DiffKeyConfig(); void copy_from(DiffKeyConfig &src); int equivalent(DiffKeyConfig &src); void interpolate(DiffKeyConfig &prev, DiffKeyConfig &next, int64_t prev_frame, int64_t next_frame, int64_t current_frame); float threshold; float slope; int do_value; }; class DiffKeyThreshold : public BC_FSlider { public: DiffKeyThreshold(DiffKey *plugin, int x, int y); int handle_event(); DiffKey *plugin; }; class DiffKeySlope : public BC_FSlider { public: DiffKeySlope(DiffKey *plugin, int x, int y); int handle_event(); DiffKey *plugin; }; class DiffKeyDoValue : public BC_CheckBox { public: DiffKeyDoValue(DiffKey *plugin, int x, int y); int handle_event(); DiffKey *plugin; }; class DiffKeyGUI : public PluginClientWindow { public: DiffKeyGUI(DiffKey *plugin); ~DiffKeyGUI(); void create_objects(); DiffKeyThreshold *threshold; DiffKeySlope *slope; DiffKeyDoValue *do_value; DiffKey *plugin; }; class DiffKeyEngine : public LoadServer { public: DiffKeyEngine(DiffKey *plugin); void init_packages(); LoadClient* new_client(); LoadPackage* new_package(); DiffKey *plugin; }; class DiffKeyClient : public LoadClient { public: DiffKeyClient(DiffKeyEngine *engine); ~DiffKeyClient(); void process_package(LoadPackage *pkg); DiffKeyEngine *engine; }; class DiffKeyPackage : public LoadPackage { public: DiffKeyPackage(); int row1; int row2; }; class DiffKey : public PluginVClient { public: DiffKey(PluginServer *server); ~DiffKey(); int process_buffer(VFrame **frame, int64_t start_position, double frame_rate); int is_realtime(); int is_multichannel(); void save_data(KeyFrame *keyframe); void read_data(KeyFrame *keyframe); void update_gui(); int handle_opengl(); PLUGIN_CLASS_MEMBERS(DiffKeyConfig) DiffKeyEngine *engine; VFrame *top_frame; VFrame *bottom_frame; }; REGISTER_PLUGIN(DiffKey) DiffKeyConfig::DiffKeyConfig() { threshold = 0.1; slope = 0; do_value = 0; } void DiffKeyConfig::copy_from(DiffKeyConfig &src) { this->threshold = src.threshold; this->slope = src.slope; this->do_value = src.do_value; } int DiffKeyConfig::equivalent(DiffKeyConfig &src) { return EQUIV(threshold, src.threshold) && EQUIV(slope, src.slope) && do_value == src.do_value; } void DiffKeyConfig::interpolate(DiffKeyConfig &prev, DiffKeyConfig &next, int64_t prev_frame, int64_t next_frame, int64_t current_frame) { double next_scale = (double)(current_frame - prev_frame) / (next_frame - prev_frame); double prev_scale = (double)(next_frame - current_frame) / (next_frame - prev_frame); this->threshold = prev.threshold * prev_scale + next.threshold * next_scale; this->slope = prev.slope * prev_scale + next.slope * next_scale; this->do_value = prev.do_value; } DiffKeyThreshold::DiffKeyThreshold(DiffKey *plugin, int x, int y) : BC_FSlider(x, y, 0, 200, 200, 0, 100, plugin->config.threshold) { this->plugin = plugin; } int DiffKeyThreshold::handle_event() { plugin->config.threshold = get_value(); plugin->send_configure_change(); return 1; } DiffKeySlope::DiffKeySlope(DiffKey *plugin, int x, int y) : BC_FSlider(x, y, 0, 200, 200, 0, 100, plugin->config.slope) { this->plugin = plugin; } int DiffKeySlope::handle_event() { plugin->config.slope = get_value(); plugin->send_configure_change(); return 1; } DiffKeyDoValue::DiffKeyDoValue(DiffKey *plugin, int x, int y) : BC_CheckBox(x, y, plugin->config.do_value, _("Use Value")) { this->plugin = plugin; } int DiffKeyDoValue::handle_event() { plugin->config.do_value = get_value(); plugin->send_configure_change(); return 1; } DiffKeyGUI::DiffKeyGUI(DiffKey *plugin) : PluginClientWindow(plugin, 320, 100, 320, 100, 0) { this->plugin = plugin; } DiffKeyGUI::~DiffKeyGUI() { } void DiffKeyGUI::create_objects() { int x = 10, y = 10; BC_Title *title; add_subwindow(title = new BC_Title(x, y, _("Threshold:"))); x += title->get_w() + 10; add_subwindow(threshold = new DiffKeyThreshold(plugin, x, y)); x = 10; y += threshold->get_h() + 10; add_subwindow(title = new BC_Title(x, y, _("Slope:"))); x += title->get_w() + 10; add_subwindow(slope = new DiffKeySlope(plugin, x, y)); x = 10; y += slope->get_h() + 10; add_subwindow(do_value = new DiffKeyDoValue(plugin, x, y)); show_window(); } DiffKey::DiffKey(PluginServer *server) : PluginVClient(server) { engine = 0; } DiffKey::~DiffKey() { delete engine; } NEW_WINDOW_MACRO(DiffKey, DiffKeyGUI) LOAD_CONFIGURATION_MACRO(DiffKey, DiffKeyConfig) const char* DiffKey::plugin_title() { return N_("Difference key"); } int DiffKey::is_realtime() { return 1; } int DiffKey::is_multichannel() { return 1; } void DiffKey::save_data(KeyFrame *keyframe) { FileXML output; output.set_shared_output(keyframe->xbuf); output.tag.set_title("DIFFKEY"); output.tag.set_property("THRESHOLD", config.threshold); output.tag.set_property("SLOPE", config.slope); output.tag.set_property("DO_VALUE", config.do_value); output.append_tag(); output.tag.set_title("/DIFFKEY"); output.append_tag(); output.append_newline(); output.terminate_string(); } void DiffKey::read_data(KeyFrame *keyframe) { FileXML input; input.set_shared_input(keyframe->xbuf); while(!input.read_tag()) { if(input.tag.title_is("DIFFKEY")) { config.threshold = input.tag.get_property("THRESHOLD", config.threshold); config.slope = input.tag.get_property("SLOPE", config.slope); config.do_value = input.tag.get_property("DO_VALUE", config.do_value); } } } void DiffKey::update_gui() { if(thread) { if(load_configuration()) { thread->window->lock_window("DiffKey::update_gui"); ((DiffKeyGUI*)thread->window)->threshold->update(config.threshold); ((DiffKeyGUI*)thread->window)->slope->update(config.slope); ((DiffKeyGUI*)thread->window)->do_value->update(config.do_value); thread->window->unlock_window(); } } } int DiffKey::process_buffer(VFrame **frame, int64_t start_position, double frame_rate) { load_configuration(); // Don't process if only 1 layer. if(get_total_buffers() < 2) { read_frame(frame[0], 0, start_position, frame_rate, get_use_opengl()); return 0; } // Read frames from 2 layers read_frame(frame[0], 0, start_position, frame_rate, get_use_opengl()); read_frame(frame[1], 1, start_position, frame_rate, get_use_opengl()); top_frame = frame[0]; bottom_frame = frame[1]; if(get_use_opengl()) return run_opengl(); if(!engine) { engine = new DiffKeyEngine(this); } engine->process_packages(); return 0; } #define DIFFKEY_VARS(plugin) \ float threshold = plugin->config.threshold / 100; \ float pad = plugin->config.slope / 100; \ float threshold_pad = threshold + pad; \ int DiffKey::handle_opengl() { #ifdef HAVE_GL static const char *diffkey_head = "uniform sampler2D tex_bg;\n" "uniform sampler2D tex_fg;\n" "uniform float threshold;\n" "uniform float pad;\n" "uniform float threshold_pad;\n" "void main()\n" "{\n" " vec4 foreground = texture2D(tex_fg, gl_TexCoord[0].st);\n" " vec4 background = texture2D(tex_bg, gl_TexCoord[0].st);\n"; static const char *colorcube = " float difference = length(foreground.rgb - background.rgb);\n"; static const char *yuv_value = " float difference = abs(foreground.r - background.r);\n"; static const char *rgb_value = " float difference = abs(" " dot(foreground.rgb, rgb_to_y_vector) - " " dot(background.rgb, rgb_to_y_vector));\n"; static const char *diffkey_tail = " vec4 result;\n" " if(difference < threshold)\n" " result.a = 0.0;\n" " else\n" " if(difference < threshold_pad)\n" " result.a = (difference - threshold) / pad;\n" " else\n" " result.a = 1.0;\n" " result.rgb = foreground.rgb;\n" " gl_FragColor = result;\n" "}\n"; top_frame->to_texture(); bottom_frame->to_texture(); top_frame->enable_opengl(); top_frame->init_screen(); const char *shader_stack[16]; memset(shader_stack,0, sizeof(shader_stack)); int current_shader = 0; int need_rgb_to_y = 0; const char *shader_frag = !config.do_value ? colorcube : BC_CModels::is_yuv(top_frame->get_color_model()) ? yuv_value : (need_rgb_to_y = 1, rgb_value); if( need_rgb_to_y ) shader_stack[current_shader++] = bc_gl_rgb_to_y; shader_stack[current_shader++] = diffkey_head; shader_stack[current_shader++] = shader_frag; shader_stack[current_shader++] = diffkey_tail; shader_stack[current_shader] = 0; unsigned int shader = VFrame::make_shader(shader_stack); DIFFKEY_VARS(this) bottom_frame->bind_texture(1); top_frame->bind_texture(0); if( shader > 0 ) { glUseProgram(shader); glUniform1i(glGetUniformLocation(shader, "tex_fg"), 0); glUniform1i(glGetUniformLocation(shader, "tex_bg"), 1); glUniform1f(glGetUniformLocation(shader, "threshold"), threshold); glUniform1f(glGetUniformLocation(shader, "pad"), pad); glUniform1f(glGetUniformLocation(shader, "threshold_pad"), threshold_pad); if( need_rgb_to_y ) BC_GL_MATRIX(shader, rgb_to_y_vector); } if(BC_CModels::components(get_output()->get_color_model()) == 3) { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); top_frame->clear_pbuffer(); } top_frame->draw_texture(); glUseProgram(0); top_frame->set_opengl_state(VFrame::SCREEN); glDisable(GL_BLEND); // Fastest way to discard output bottom_frame->set_opengl_state(VFrame::UNKNOWN); // kludge ahead top_frame->screen_to_ram(); #endif return 0; } DiffKeyEngine::DiffKeyEngine(DiffKey *plugin) : LoadServer(plugin->get_project_smp() + 1, plugin->get_project_smp() + 1) { this->plugin = plugin; } void DiffKeyEngine::init_packages() { int increment = plugin->top_frame->get_h() / get_total_packages() + 1; int y = 0; for(int i = 0; i < get_total_packages(); i++) { DiffKeyPackage *pkg = (DiffKeyPackage*)get_package(i); pkg->row1 = y; pkg->row2 = MIN(y + increment, plugin->top_frame->get_h()); y += increment; } } LoadClient* DiffKeyEngine::new_client() { return new DiffKeyClient(this); } LoadPackage* DiffKeyEngine::new_package() { return new DiffKeyPackage; } DiffKeyClient::DiffKeyClient(DiffKeyEngine *engine) : LoadClient(engine) { this->engine = engine; } DiffKeyClient::~DiffKeyClient() { } void DiffKeyClient::process_package(LoadPackage *ptr) { DiffKeyPackage *pkg = (DiffKeyPackage*)ptr; DiffKey *plugin = engine->plugin; int w = plugin->top_frame->get_w(); #define RGB_TO_VALUE(r, g, b) YUV::yuv.rgb_to_y_f((r),(g),(b)) #define DIFFKEY_MACRO(type, components, max, chroma_offset) \ { \ \ for(int i = pkg->row1; i < pkg->row2; i++) \ { \ type *top_row = (type*)plugin->top_frame->get_rows()[i]; \ type *bottom_row = (type*)plugin->bottom_frame->get_rows()[i]; \ \ for(int j = 0; j < w; j++) \ { \ float a = 1.0; \ \ /* Test for value in range */ \ if(plugin->config.do_value) \ { \ float top_value; \ float bottom_value; \ \ /* Convert pixel data into floating point value */ \ if(chroma_offset) \ { \ float top_r = (float)top_row[0] / max; \ float bottom_r = (float)bottom_row[0] / max; \ top_value = top_r; \ bottom_value = bottom_r; \ } \ else \ { \ float top_r = (float)top_row[0] / max; \ float top_g = (float)top_row[1] / max; \ float top_b = (float)top_row[2] / max; \ top_g -= (float)chroma_offset / max; \ top_b -= (float)chroma_offset / max; \ \ float bottom_r = (float)bottom_row[0] / max; \ float bottom_g = (float)bottom_row[1] / max; \ float bottom_b = (float)bottom_row[2] / max; \ bottom_g -= (float)chroma_offset / max; \ bottom_b -= (float)chroma_offset / max; \ \ top_value = RGB_TO_VALUE(top_r, top_g, top_b); \ bottom_value = RGB_TO_VALUE(bottom_r, bottom_g, bottom_b); \ } \ \ float min_v = bottom_value - threshold; \ float max_v = bottom_value + threshold; \ \ /* Full transparency if in range */ \ if(top_value >= min_v && top_value < max_v) \ { \ a = 0; \ } \ else \ /* Phased out if below or above range */ \ if(top_value < min_v) \ { \ if(min_v - top_value < pad) \ a = (min_v - top_value) / pad; \ } \ else \ if(top_value - max_v < pad) \ a = (top_value - max_v) / pad; \ } \ else \ /* Use color cube */ \ { \ float top_r = (float)top_row[0] / max; \ float top_g = (float)top_row[1] / max; \ float top_b = (float)top_row[2] / max; \ top_g -= (float)chroma_offset / max; \ top_b -= (float)chroma_offset / max; \ \ float bottom_r = (float)bottom_row[0] / max; \ float bottom_g = (float)bottom_row[1] / max; \ float bottom_b = (float)bottom_row[2] / max; \ bottom_g -= (float)chroma_offset / max; \ bottom_b -= (float)chroma_offset / max; \ \ \ float difference = sqrt(SQR(top_r - bottom_r) + \ SQR(top_g - bottom_g) + \ SQR(top_b - bottom_b)); \ \ if(difference < threshold) \ { \ a = 0; \ } \ else \ if(difference < threshold_pad) \ { \ a = (difference - threshold) / pad; \ } \ } \ \ /* multiply alpha */ \ if(components == 4) \ { \ top_row[3] = MIN((type)(a * max), top_row[3]); \ } \ else \ { \ top_row[0] = (type)(a * top_row[0]); \ top_row[1] = (type)(a * (top_row[1] - chroma_offset) + chroma_offset); \ top_row[2] = (type)(a * (top_row[2] - chroma_offset) + chroma_offset); \ } \ \ top_row += components; \ bottom_row += components; \ } \ } \ } DIFFKEY_VARS(plugin) switch(plugin->top_frame->get_color_model()) { case BC_RGB_FLOAT: DIFFKEY_MACRO(float, 3, 1.0, 0); break; case BC_RGBA_FLOAT: DIFFKEY_MACRO(float, 4, 1.0, 0); break; case BC_RGB888: DIFFKEY_MACRO(unsigned char, 3, 0xff, 0); break; case BC_RGBA8888: DIFFKEY_MACRO(unsigned char, 4, 0xff, 0); break; case BC_YUV888: DIFFKEY_MACRO(unsigned char, 3, 0xff, 0x80); break; case BC_YUVA8888: DIFFKEY_MACRO(unsigned char, 4, 0xff, 0x80); break; } } DiffKeyPackage::DiffKeyPackage() : LoadPackage() { } #endif