/* * 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 * */ #include #include #include "threshold.h" #include "bccolors.h" #include "clip.h" #include "bchash.h" #include "filexml.h" #include "histogramengine.h" #include "language.h" #include "playback3d.h" #include "thresholdwindow.h" #include "vframe.h" using ::std::string; ThresholdConfig::ThresholdConfig() { reset(); } int ThresholdConfig::equivalent(ThresholdConfig &that) { return EQUIV(min, that.min) && EQUIV(max, that.max) && plot == that.plot && low_color == that.low_color && mid_color == that.mid_color && high_color == that.high_color; } void ThresholdConfig::copy_from(ThresholdConfig &that) { min = that.min; max = that.max; plot = that.plot; low_color = that.low_color; mid_color = that.mid_color; high_color = that.high_color; } // General purpose scale function. template T interpolate(const T & prev, const double & prev_scale, const T & next, const double & next_scale) { return static_cast(prev * prev_scale + next * next_scale); } void ThresholdConfig::interpolate(ThresholdConfig &prev, ThresholdConfig &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); min = ::interpolate(prev.min, prev_scale, next.min, next_scale); max = ::interpolate(prev.max, prev_scale, next.max, next_scale); plot = prev.plot; low_color = ::interpolate(prev.low_color, prev_scale, next.low_color, next_scale); mid_color = ::interpolate(prev.mid_color, prev_scale, next.mid_color, next_scale); high_color = ::interpolate(prev.high_color, prev_scale, next.high_color, next_scale); } void ThresholdConfig::reset() { min = 0.0; max = 1.0; plot = 1; low_color.set (0x0, 0x0, 0x0, 0xff); mid_color.set (0xff, 0xff, 0xff, 0xff); high_color.set(0x0, 0x0, 0x0, 0xff); } void ThresholdConfig::boundaries() { CLAMP(min, HISTOGRAM_MIN, max); CLAMP(max, min, HISTOGRAM_MAX); } REGISTER_PLUGIN(ThresholdMain) ThresholdMain::ThresholdMain(PluginServer *server) : PluginVClient(server) { engine = 0; threshold_engine = 0; } ThresholdMain::~ThresholdMain() { delete engine; delete threshold_engine; } const char* ThresholdMain::plugin_title() { return N_("Threshold"); } int ThresholdMain::is_realtime() { return 1; } LOAD_CONFIGURATION_MACRO(ThresholdMain, ThresholdConfig) int ThresholdMain::process_buffer(VFrame *frame, int64_t start_position, double frame_rate) { load_configuration(); int use_opengl = get_use_opengl() && (!config.plot || !gui_open()); read_frame(frame, 0, get_source_position(), get_framerate(), use_opengl); if(use_opengl) return run_opengl(); send_render_gui(frame); if(!threshold_engine) threshold_engine = new ThresholdEngine(this); threshold_engine->process_packages(frame); return 0; } void ThresholdMain::save_data(KeyFrame *keyframe) { FileXML output; output.set_shared_output(keyframe->xbuf); output.tag.set_title("THRESHOLD"); output.tag.set_property("MIN", config.min); output.tag.set_property("MAX", config.max); output.tag.set_property("PLOT", config.plot); config.low_color.set_property(output.tag, "LOW_COLOR"); config.mid_color.set_property(output.tag, "MID_COLOR"); config.high_color.set_property(output.tag, "HIGH_COLOR"); output.append_tag(); output.tag.set_title("/THRESHOLD"); output.append_tag(); output.append_newline(); output.terminate_string(); } void ThresholdMain::read_data(KeyFrame *keyframe) { FileXML input; input.set_shared_input(keyframe->xbuf); int result = 0; while(!result) { result = input.read_tag(); if(!result) { config.min = input.tag.get_property("MIN", config.min); config.max = input.tag.get_property("MAX", config.max); config.plot = input.tag.get_property("PLOT", config.plot); config.low_color = config.low_color.get_property(input.tag, "LOW_COLOR"); config.mid_color = config.mid_color.get_property(input.tag, "MID_COLOR"); config.high_color = config.high_color.get_property(input.tag, "HIGH_COLOR"); } } config.boundaries(); } NEW_WINDOW_MACRO(ThresholdMain,ThresholdWindow); void ThresholdMain::update_gui() { if(thread) { thread->window->lock_window("ThresholdMain::update_gui"); if(load_configuration()) { ThresholdWindow *window = (ThresholdWindow*) thread->window; window->min->update(config.min); window->max->update(config.max); window->plot->update(config.plot); window->update_low_color(); window->update_mid_color(); window->update_high_color(); window->low_color_thread->update_gui(config.low_color.getRGB(), config.low_color.a); window->mid_color_thread->update_gui(config.mid_color.getRGB(), config.mid_color.a); window->high_color_thread->update_gui(config.high_color.getRGB(), config.high_color.a); } thread->window->unlock_window(); } } void ThresholdMain::render_gui(void *data) { if(thread) { calculate_histogram((VFrame*)data); ThresholdWindow *window = (ThresholdWindow*) thread->window; window->lock_window("ThresholdMain::render_gui"); window->canvas->draw(); window->unlock_window(); } } void ThresholdMain::calculate_histogram(VFrame *frame) { if(!engine) engine = new HistogramEngine(get_project_smp() + 1, get_project_smp() + 1); engine->process_packages(frame); } int ThresholdMain::handle_opengl() { #ifdef HAVE_GL static const char *rgb_shader = "uniform sampler2D tex;\n" "uniform float min;\n" "uniform float max;\n" "uniform vec4 low_color;\n" "uniform vec4 mid_color;\n" "uniform vec4 high_color;\n" "uniform vec3 rgb_to_y_vector;\n" "uniform float yminf;\n" "void main()\n" "{\n" " vec4 pixel = texture2D(tex, gl_TexCoord[0].st);\n" " float v = dot(pixel.rgb, rgb_to_y_vector) + yminf;\n" " if(v < min)\n" " pixel = low_color;\n" " else if(v < max)\n" " pixel = mid_color;\n" " else\n" " pixel = high_color;\n" " gl_FragColor = pixel;\n" "}\n"; static const char *yuv_shader = "uniform sampler2D tex;\n" "uniform float min;\n" "uniform float max;\n" "uniform vec4 low_color;\n" "uniform vec4 mid_color;\n" "uniform vec4 high_color;\n" "void main()\n" "{\n" " vec4 pixel = texture2D(tex, gl_TexCoord[0].st);\n" " if(pixel.r < min)\n" " pixel = low_color;\n" " else if(pixel.r < max)\n" " pixel = mid_color;\n" " else\n" " pixel = high_color;\n" " gl_FragColor = pixel;\n" "}\n"; get_output()->to_texture(); get_output()->enable_opengl(); int color_model = get_output()->get_color_model(); bool is_yuv = BC_CModels::is_yuv(color_model); bool has_alpha = BC_CModels::has_alpha(color_model); unsigned int shader = VFrame::make_shader(0, is_yuv ? yuv_shader : rgb_shader, 0); if( shader > 0 ) { glUseProgram(shader); glUniform1i(glGetUniformLocation(shader, "tex"), 0); glUniform1f(glGetUniformLocation(shader, "min"), config.min); glUniform1f(glGetUniformLocation(shader, "max"), config.max); if (is_yuv) { float y_low, u_low, v_low; float y_mid, u_mid, v_mid; float y_high, u_high, v_high; YUV::yuv.rgb_to_yuv_f( (float)config.low_color.r / 0xff, (float)config.low_color.g / 0xff, (float)config.low_color.b / 0xff, y_low, u_low, v_low); u_low += 0.5; v_low += 0.5; YUV::yuv.rgb_to_yuv_f( (float)config.mid_color.r / 0xff, (float)config.mid_color.g / 0xff, (float)config.mid_color.b / 0xff, y_mid, u_mid, v_mid); u_mid += 0.5; v_mid += 0.5; YUV::yuv.rgb_to_yuv_f( (float)config.high_color.r / 0xff, (float)config.high_color.g / 0xff, (float)config.high_color.b / 0xff, y_high, u_high, v_high); u_high += 0.5; v_high += 0.5; glUniform4f(glGetUniformLocation(shader, "low_color"), y_low, u_low, v_low, has_alpha ? (float)config.low_color.a / 0xff : 1.0); glUniform4f(glGetUniformLocation(shader, "mid_color"), y_mid, u_mid, v_mid, has_alpha ? (float)config.mid_color.a / 0xff : 1.0); glUniform4f(glGetUniformLocation(shader, "high_color"), y_high, u_high, v_high, has_alpha ? (float)config.high_color.a / 0xff : 1.0); } else { glUniform4f(glGetUniformLocation(shader, "low_color"), (float)config.low_color.r / 0xff, (float)config.low_color.g / 0xff, (float)config.low_color.b / 0xff, has_alpha ? (float)config.low_color.a / 0xff : 1.0); glUniform4f(glGetUniformLocation(shader, "mid_color"), (float)config.mid_color.r / 0xff, (float)config.mid_color.g / 0xff, (float)config.mid_color.b / 0xff, has_alpha ? (float)config.mid_color.a / 0xff : 1.0); glUniform4f(glGetUniformLocation(shader, "high_color"), (float)config.high_color.r / 0xff, (float)config.high_color.g / 0xff, (float)config.high_color.b / 0xff, has_alpha ? (float)config.high_color.a / 0xff : 1.0); BC_GL_RGB_TO_Y(shader); } } get_output()->init_screen(); get_output()->bind_texture(0); get_output()->draw_texture(); glUseProgram(0); get_output()->set_opengl_state(VFrame::SCREEN); #endif return 0; } ThresholdPackage::ThresholdPackage() : LoadPackage() { start = end = 0; } ThresholdUnit::ThresholdUnit(ThresholdEngine *server) : LoadClient(server) { this->server = server; } // Coerces pixel component to int. static inline int get_component(unsigned char v) { return (v << 8) | v; } static inline int get_component(float v) { return (int)(v * 0xffff); } static inline int get_component(uint16_t v) { return v; } // Rescales value in range [0, 255] to range appropriate to TYPE. template static TYPE scale_to_range(int v) { return v; // works for unsigned char, override for the rest. } template<> inline float scale_to_range(int v) { return (float) v / 0xff; } template<> inline uint16_t scale_to_range(int v) { return v << 8 | v; } static inline void rgb_to_yuv(unsigned char r, unsigned char g, unsigned char b, unsigned char & y, unsigned char & u, unsigned char & v) { YUV::yuv.rgb_to_yuv_8(r, g, b, y, u, v); } static inline void rgb_to_yuv(float r, float g, float b, float & y, float & u, float & v) { YUV::yuv.rgb_to_yuv_f(r, g, b, y, u, v); } static inline void rgb_to_yuv(uint16_t r, uint16_t g, uint16_t b, uint16_t & y, uint16_t & u, uint16_t & v) { YUV::yuv.rgb_to_yuv_16(r, g, b, y, u, v); } template void ThresholdUnit::render_data(LoadPackage *package) { const ThresholdPackage *pkg = (ThresholdPackage*)package; const ThresholdConfig *config = & server->plugin->config; VFrame *data = server->data; const int min = (int)(config->min * 0xffff); const int max = (int)(config->max * 0xffff); const int w = data->get_w(); //const int h = data->get_h(); const TYPE r_low = scale_to_range(config->low_color.r); const TYPE g_low = scale_to_range(config->low_color.g); const TYPE b_low = scale_to_range(config->low_color.b); const TYPE a_low = scale_to_range(config->low_color.a); const TYPE r_mid = scale_to_range(config->mid_color.r); const TYPE g_mid = scale_to_range(config->mid_color.g); const TYPE b_mid = scale_to_range(config->mid_color.b); const TYPE a_mid = scale_to_range(config->mid_color.a); const TYPE r_high = scale_to_range(config->high_color.r); const TYPE g_high = scale_to_range(config->high_color.g); const TYPE b_high = scale_to_range(config->high_color.b); const TYPE a_high = scale_to_range(config->high_color.a); TYPE y_low, u_low, v_low; TYPE y_mid, u_mid, v_mid; TYPE y_high, u_high, v_high; if (USE_YUV) { rgb_to_yuv(r_low, g_low, b_low, y_low, u_low, v_low); rgb_to_yuv(r_mid, g_mid, b_mid, y_mid, u_mid, v_mid); rgb_to_yuv(r_high, g_high, b_high, y_high, u_high, v_high); } for(int i = pkg->start; i < pkg->end; i++) { TYPE *in_row = (TYPE*)data->get_rows()[i]; TYPE *out_row = in_row; for(int j = 0; j < w; j++) { if (USE_YUV) { const int y = get_component(in_row[0]); if (y < min) { *out_row++ = y_low; *out_row++ = u_low; *out_row++ = v_low; if(COMPONENTS == 4) *out_row++ = a_low; } else if (y < max) { *out_row++ = y_mid; *out_row++ = u_mid; *out_row++ = v_mid; if(COMPONENTS == 4) *out_row++ = a_mid; } else { *out_row++ = y_high; *out_row++ = u_high; *out_row++ = v_high; if(COMPONENTS == 4) *out_row++ = a_high; } } else { const int r = get_component(in_row[0]); const int g = get_component(in_row[1]); const int b = get_component(in_row[2]); const int y = (r * 76 + g * 150 + b * 29) >> 8; if (y < min) { *out_row++ = r_low; *out_row++ = g_low; *out_row++ = b_low; if(COMPONENTS == 4) *out_row++ = a_low; } else if (y < max) { *out_row++ = r_mid; *out_row++ = g_mid; *out_row++ = b_mid; if(COMPONENTS == 4) *out_row++ = a_mid; } else { *out_row++ = r_high; *out_row++ = g_high; *out_row++ = b_high; if(COMPONENTS == 4) *out_row++ = a_high; } } in_row += COMPONENTS; } } } void ThresholdUnit::process_package(LoadPackage *package) { switch(server->data->get_color_model()) { case BC_RGB888: render_data(package); break; case BC_RGB_FLOAT: render_data(package); break; case BC_RGBA8888: render_data(package); break; case BC_RGBA_FLOAT: render_data(package); break; case BC_YUV888: render_data(package); break; case BC_YUVA8888: render_data(package); break; case BC_YUV161616: render_data(package); break; case BC_YUVA16161616: render_data(package); break; } } ThresholdEngine::ThresholdEngine(ThresholdMain *plugin) : LoadServer(plugin->get_project_smp() + 1, plugin->get_project_smp() + 1) { this->plugin = plugin; } ThresholdEngine::~ThresholdEngine() { } void ThresholdEngine::process_packages(VFrame *data) { this->data = data; LoadServer::process_packages(); } void ThresholdEngine::init_packages() { for(int i = 0; i < get_total_packages(); i++) { ThresholdPackage *package = (ThresholdPackage*)get_package(i); package->start = data->get_h() * i / get_total_packages(); package->end = data->get_h() * (i + 1) / get_total_packages(); } } LoadClient* ThresholdEngine::new_client() { return (LoadClient*)new ThresholdUnit(this); } LoadPackage* ThresholdEngine::new_package() { return (LoadPackage*)new HistogramPackage; } RGBA::RGBA() { r = g = b = a = 0; } RGBA::RGBA(int r, int g, int b, int a) { this->r = r; this->g = g; this->b = b; this->a = a; } void RGBA::set(int r, int g, int b, int a) { this->r = r; this->g = g; this->b = b; this->a = a; } void RGBA::set(int rgb, int alpha) { r = (rgb & 0xff0000) >> 16; g = (rgb & 0xff00) >> 8; b = (rgb & 0xff); a = alpha; } int RGBA::getRGB() const { return r << 16 | g << 8 | b; } static void init_RGBA_keys(const char * prefix, string & r_s, string & g_s, string & b_s, string & a_s) { r_s = prefix; g_s = prefix; b_s = prefix; a_s = prefix; r_s += "_R"; g_s += "_G"; b_s += "_B"; a_s += "_A"; } void RGBA::set_property(XMLTag & tag, const char * prefix) const { string r_s, g_s, b_s, a_s; init_RGBA_keys(prefix, r_s, g_s, b_s, a_s); tag.set_property(const_cast(r_s.c_str()), r); tag.set_property(const_cast(g_s.c_str()), g); tag.set_property(const_cast(b_s.c_str()), b); tag.set_property(const_cast(a_s.c_str()), a); } RGBA RGBA::get_property(XMLTag & tag, const char * prefix) const { string r_s, g_s, b_s, a_s; init_RGBA_keys(prefix, r_s, g_s, b_s, a_s); return RGBA(tag.get_property(const_cast(r_s.c_str()), r), tag.get_property(const_cast(g_s.c_str()), g), tag.get_property(const_cast(b_s.c_str()), b), tag.get_property(const_cast(a_s.c_str()), a)); } bool operator==(const RGBA & a, const RGBA & b) { return a.r == b.r && a.g == b.g && a.b == b.b && a.a == b.a; } template<> RGBA interpolate(const RGBA & prev_color, const double & prev_scale, const RGBA &next_color, const double & next_scale) { return RGBA(interpolate(prev_color.r, prev_scale, next_color.r, next_scale), interpolate(prev_color.g, prev_scale, next_color.g, next_scale), interpolate(prev_color.b, prev_scale, next_color.b, next_scale), interpolate(prev_color.a, prev_scale, next_color.a, next_scale)); }