Merge CV, ver=5.1; ops/methods from HV, and interface from CV where possible
[goodguy/history.git] / cinelerra-5.1 / plugins / threshold / threshold.C
diff --git a/cinelerra-5.1/plugins/threshold/threshold.C b/cinelerra-5.1/plugins/threshold/threshold.C
new file mode 100644 (file)
index 0000000..b61fb48
--- /dev/null
@@ -0,0 +1,763 @@
+
+/*
+ * CINELERRA
+ * Copyright (C) 2008 Adam Williams <broadcast at earthling dot net>
+ * 
+ * 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 <string>
+#include <string.h>
+
+#include "clip.h"
+#include "bchash.h"
+#include "filexml.h"
+#include "histogramengine.h"
+#include "language.h"
+#include "cicolors.h"
+#include "threshold.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<typename T>
+T interpolate(const T & prev, const double & prev_scale, const T & next, const double & next_scale)
+{
+       return static_cast<T>(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 _("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->get_data(), MESSAGESIZE);
+       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;
+       const char *data = keyframe->get_data();
+       input.set_shared_input((char *)data, strlen(data));
+       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"
+               "void main()\n"
+               "{\n"
+               "       vec4 pixel = texture2D(tex, gl_TexCoord[0].st);\n"
+               "       float v = dot(pixel.rgb, vec3(0.299, 0.587, 0.114));\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();
+
+       unsigned int shader = 0;
+       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);
+       if(is_yuv)
+               shader = VFrame::make_shader(0, yuv_shader, 0);
+       else
+               shader = VFrame::make_shader(0, 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::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::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::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);
+               }
+       }
+
+       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<typename TYPE>
+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(YUV & yuv,
+                             unsigned char   r, unsigned char   g, unsigned char   b,
+                             unsigned char & y, unsigned char & u, unsigned char & v)
+{
+       yuv.rgb_to_yuv_8(r, g, b, y, u, v);
+}
+
+static inline void rgb_to_yuv(YUV & yuv,
+                             float   r, float   g, float   b,
+                             float & y, float & u, float & v)
+{
+       yuv.rgb_to_yuv_f(r, g, b, y, u, v);
+}
+
+static inline void rgb_to_yuv(YUV & yuv,
+                             uint16_t   r, uint16_t   g, uint16_t   b,
+                             uint16_t & y, uint16_t & u, uint16_t & v)
+{
+       yuv.rgb_to_yuv_16(r, g, b, y, u, v);
+}
+
+template<typename TYPE, int COMPONENTS, bool USE_YUV>
+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<TYPE>(config->low_color.r);
+       const TYPE g_low = scale_to_range<TYPE>(config->low_color.g);
+       const TYPE b_low = scale_to_range<TYPE>(config->low_color.b);
+       const TYPE a_low = scale_to_range<TYPE>(config->low_color.a);
+       
+       const TYPE r_mid = scale_to_range<TYPE>(config->mid_color.r);
+       const TYPE g_mid = scale_to_range<TYPE>(config->mid_color.g);
+       const TYPE b_mid = scale_to_range<TYPE>(config->mid_color.b);
+       const TYPE a_mid = scale_to_range<TYPE>(config->mid_color.a);
+       
+       const TYPE r_high = scale_to_range<TYPE>(config->high_color.r);
+       const TYPE g_high = scale_to_range<TYPE>(config->high_color.g);
+       const TYPE b_high = scale_to_range<TYPE>(config->high_color.b);
+       const TYPE a_high = scale_to_range<TYPE>(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(*server->yuv, r_low,  g_low,  b_low,  y_low,  u_low,  v_low);
+               rgb_to_yuv(*server->yuv, r_mid,  g_mid,  b_mid,  y_mid,  u_mid,  v_mid);
+               rgb_to_yuv(*server->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<unsigned char, 3, false>(package);
+                       break;
+
+               case BC_RGB_FLOAT:
+                       render_data<float, 3, false>(package);
+                       break;
+
+               case BC_RGBA8888:
+                       render_data<unsigned char, 4, false>(package);
+                       break;
+
+               case BC_RGBA_FLOAT:
+                       render_data<float, 4, false>(package);
+                       break;
+
+               case BC_YUV888:
+                       render_data<unsigned char, 3, true>(package);
+                       break;
+
+               case BC_YUVA8888:
+                       render_data<unsigned char, 4, true>(package);
+                       break;
+
+               case BC_YUV161616:
+                       render_data<uint16_t, 3, true>(package);
+                       break;
+
+               case BC_YUVA16161616:
+                       render_data<uint16_t, 4, true>(package);
+                       break;
+       }
+}
+
+
+
+
+
+
+
+
+
+
+
+ThresholdEngine::ThresholdEngine(ThresholdMain *plugin)
+ : LoadServer(plugin->get_project_smp() + 1,
+       plugin->get_project_smp() + 1)
+{
+       this->plugin = plugin;
+       yuv = new YUV;
+}
+
+ThresholdEngine::~ThresholdEngine()
+{
+       delete yuv;
+}
+
+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<char *>(r_s.c_str()), r);
+       tag.set_property(const_cast<char *>(g_s.c_str()), g);
+       tag.set_property(const_cast<char *>(b_s.c_str()), b);
+       tag.set_property(const_cast<char *>(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<char *>(r_s.c_str()), r),
+                   tag.get_property(const_cast<char *>(g_s.c_str()), g),
+                   tag.get_property(const_cast<char *>(b_s.c_str()), b),
+                   tag.get_property(const_cast<char *>(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));
+}