Merge CV, ver=5.1; ops/methods from HV, and interface from CV where possible
[goodguy/history.git] / cinelerra-5.1 / plugins / colorbalance / colorbalance.C
diff --git a/cinelerra-5.1/plugins/colorbalance/colorbalance.C b/cinelerra-5.1/plugins/colorbalance/colorbalance.C
new file mode 100644 (file)
index 0000000..13d5e62
--- /dev/null
@@ -0,0 +1,664 @@
+
+/*
+ * 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 "filexml.h"
+#include "clip.h"
+#include "colorbalance.h"
+#include "bchash.h"
+#include "language.h"
+#include "playback3d.h"
+
+#include "aggregated.h"
+#include "../interpolate/aggregated.h"
+#include "../gamma/aggregated.h"
+
+#include <stdio.h>
+#include <string.h>
+
+// 1000 corresponds to (1.0 + MAX_COLOR) * input
+#define MAX_COLOR 1.0
+
+REGISTER_PLUGIN(ColorBalanceMain)
+
+
+
+ColorBalanceConfig::ColorBalanceConfig()
+{
+       cyan = 0;
+       magenta = 0;
+       yellow = 0;
+       lock_params = 0;
+    preserve = 0;
+}
+
+int ColorBalanceConfig::equivalent(ColorBalanceConfig &that)
+{
+       return (cyan == that.cyan && 
+               magenta == that.magenta && 
+               yellow == that.yellow && 
+               lock_params == that.lock_params && 
+       preserve == that.preserve);
+}
+
+void ColorBalanceConfig::copy_from(ColorBalanceConfig &that)
+{
+       cyan = that.cyan;
+       magenta = that.magenta;
+       yellow = that.yellow;
+       lock_params = that.lock_params;
+    preserve = that.preserve;
+}
+
+void ColorBalanceConfig::interpolate(ColorBalanceConfig &prev, 
+       ColorBalanceConfig &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->cyan = prev.cyan * prev_scale + next.cyan * next_scale;
+       this->magenta = prev.magenta * prev_scale + next.magenta * next_scale;
+       this->yellow = prev.yellow * prev_scale + next.yellow * next_scale;
+       this->preserve = prev.preserve;
+       this->lock_params = prev.lock_params;
+}
+
+
+
+
+
+
+
+
+
+
+ColorBalanceEngine::ColorBalanceEngine(ColorBalanceMain *plugin)
+ : Thread()
+{
+       this->plugin = plugin;
+       last_frame = 0;
+       set_synchronous(1);
+}
+
+ColorBalanceEngine::~ColorBalanceEngine()
+{
+       last_frame = 1;
+       input_lock.unlock();
+       Thread::join();
+}
+
+
+int ColorBalanceEngine::start_process_frame(VFrame *output, VFrame *input, int row_start, int row_end)
+{
+       this->output = output;
+       this->input = input;
+       this->row_start = row_start;
+       this->row_end = row_end;
+       input_lock.unlock();
+       return 0;
+}
+
+
+int ColorBalanceEngine::wait_process_frame()
+{
+       output_lock.lock("ColorBalanceEngine::wait_process_frame");
+       return 0;
+}
+
+void ColorBalanceEngine::run()
+{
+       while(1)
+       {
+               input_lock.lock("ColorBalanceEngine::run");
+               if(last_frame)
+               {
+                       output_lock.unlock();
+                       return;
+               }
+
+#define PROCESS(yuvtorgb,  \
+       rgbtoyuv,  \
+       r_lookup,  \
+       g_lookup,  \
+       b_lookup,  \
+       type,  \
+       max,  \
+       components,  \
+       do_yuv) \
+{ \
+       int j, k; \
+       int y, cr, cb, r, g, b, r_n, g_n, b_n; \
+       float h, s, v, h_old, s_old, r_f, g_f, b_f; \
+       type **input_rows, **output_rows; \
+       input_rows = (type**)input->get_rows(); \
+       output_rows = (type**)output->get_rows(); \
+ \
+       for(j = row_start; j < row_end; j++) \
+       { \
+               for(k = 0; k < input->get_w() * components; k += components) \
+               { \
+                       if(do_yuv) \
+                       { \
+                               y = input_rows[j][k]; \
+                               cb = input_rows[j][k + 1]; \
+                               cr = input_rows[j][k + 2]; \
+                               yuvtorgb(r, g, b, y, cb, cr); \
+                       } \
+                       else \
+                       { \
+                               r = input_rows[j][k]; \
+                               g = input_rows[j][k + 1]; \
+                               b = input_rows[j][k + 2]; \
+                       } \
+ \
+                       r_n = plugin->r_lookup[r]; \
+                       g_n = plugin->g_lookup[g]; \
+                       b_n = plugin->b_lookup[b]; \
+ \
+                       if(plugin->config.preserve) \
+                       { \
+                               HSV::rgb_to_hsv((float)r_n, (float)g_n, (float)b_n, h, s, v); \
+                               HSV::rgb_to_hsv((float)r, (float)g, (float)b, h_old, s_old, v); \
+                               HSV::hsv_to_rgb(r_f, g_f, b_f, h, s, v); \
+                               r = (type)r_f; \
+                               g = (type)g_f; \
+                               b = (type)b_f; \
+                       } \
+                       else \
+                       { \
+                               r = r_n; \
+                               g = g_n; \
+                               b = b_n; \
+                       } \
+ \
+                       if(do_yuv) \
+                       { \
+                               rgbtoyuv(CLAMP(r, 0, max), CLAMP(g, 0, max), CLAMP(b, 0, max), y, cb, cr); \
+                               output_rows[j][k] = y; \
+                               output_rows[j][k + 1] = cb; \
+                               output_rows[j][k + 2] = cr; \
+                       } \
+                       else \
+                       { \
+                               output_rows[j][k] = CLAMP(r, 0, max); \
+                               output_rows[j][k + 1] = CLAMP(g, 0, max); \
+                               output_rows[j][k + 2] = CLAMP(b, 0, max); \
+                       } \
+               } \
+       } \
+}
+
+#define PROCESS_F(components) \
+{ \
+       int j, k; \
+       float r, g, b, r_n, g_n, b_n; \
+       float h, s, v, h_old, s_old, r_f, g_f, b_f; \
+       float **input_rows, **output_rows; \
+       input_rows = (float**)input->get_rows(); \
+       output_rows = (float**)output->get_rows(); \
+       cyan_f = plugin->calculate_transfer(plugin->config.cyan); \
+       magenta_f = plugin->calculate_transfer(plugin->config.magenta); \
+       yellow_f = plugin->calculate_transfer(plugin->config.yellow); \
+ \
+printf("PROCESS_F %f\n", cyan_f); \
+       for(j = row_start; j < row_end; j++) \
+       { \
+               for(k = 0; k < input->get_w() * components; k += components) \
+               { \
+                       r = input_rows[j][k]; \
+                       g = input_rows[j][k + 1]; \
+                       b = input_rows[j][k + 2]; \
+ \
+                       r_n = r * cyan_f; \
+                       g_n = g * magenta_f; \
+                       b_n = b * yellow_f; \
+ \
+                       if(plugin->config.preserve) \
+                       { \
+                               HSV::rgb_to_hsv(r_n, g_n, b_n, h, s, v); \
+                               HSV::rgb_to_hsv(r, g, b, h_old, s_old, v); \
+                               HSV::hsv_to_rgb(r_f, g_f, b_f, h, s, v); \
+                               r = (float)r_f; \
+                               g = (float)g_f; \
+                               b = (float)b_f; \
+                       } \
+                       else \
+                       { \
+                               r = r_n; \
+                               g = g_n; \
+                               b = b_n; \
+                       } \
+ \
+                       output_rows[j][k] = r; \
+                       output_rows[j][k + 1] = g; \
+                       output_rows[j][k + 2] = b; \
+               } \
+       } \
+}
+
+               switch(input->get_color_model())
+               {
+                       case BC_RGB888:
+                               PROCESS(yuv.yuv_to_rgb_8, yuv.rgb_to_yuv_8, 
+                                       r_lookup_8, g_lookup_8, b_lookup_8, 
+                                       unsigned char, 0xff, 3, 0);
+                               break;
+
+                       case BC_RGB_FLOAT:
+                               PROCESS_F(3);
+                               break;
+
+                       case BC_YUV888:
+                               PROCESS(yuv.yuv_to_rgb_8, yuv.rgb_to_yuv_8, 
+                                       r_lookup_8, g_lookup_8, b_lookup_8, 
+                                       unsigned char, 0xff, 3, 1);
+                               break;
+                       
+                       case BC_RGBA_FLOAT:
+                               PROCESS_F(4);
+                               break;
+
+                       case BC_RGBA8888:
+                               PROCESS(yuv.yuv_to_rgb_8, yuv.rgb_to_yuv_8, 
+                                       r_lookup_8, g_lookup_8, b_lookup_8, 
+                                       unsigned char, 0xff, 4, 0);
+                               break;
+
+                       case BC_YUVA8888:
+                               PROCESS(yuv.yuv_to_rgb_8, yuv.rgb_to_yuv_8, 
+                                       r_lookup_8, g_lookup_8, b_lookup_8, 
+                                       unsigned char, 0xff, 4, 1);
+                               break;
+                       
+                       case BC_YUV161616:
+                               PROCESS(yuv.yuv_to_rgb_16, yuv.rgb_to_yuv_16, 
+                                       r_lookup_16, g_lookup_16, b_lookup_16, 
+                                       u_int16_t, 0xffff, 3, 1);
+                               break;
+
+                       case BC_YUVA16161616:
+                               PROCESS(yuv.yuv_to_rgb_16, yuv.rgb_to_yuv_16, 
+                                       r_lookup_16, g_lookup_16, b_lookup_16, 
+                                       u_int16_t, 0xffff, 4, 1);
+                               break;
+               }
+
+               
+               
+               output_lock.unlock();
+       }
+}
+
+
+
+
+ColorBalanceMain::ColorBalanceMain(PluginServer *server)
+ : PluginVClient(server)
+{
+       need_reconfigure = 1;
+       engine = 0;
+       
+}
+
+ColorBalanceMain::~ColorBalanceMain()
+{
+       
+
+
+       if(engine)
+       {
+               for(int i = 0; i < total_engines; i++)
+               {
+                       delete engine[i];
+               }
+               delete [] engine;
+       }
+}
+
+const char* ColorBalanceMain::plugin_title() { return _("Color Balance"); }
+int ColorBalanceMain::is_realtime() { return 1; }
+
+
+int ColorBalanceMain::reconfigure()
+{
+       float r_scale = calculate_transfer(config.cyan);
+       float g_scale = calculate_transfer(config.magenta);
+       float b_scale = calculate_transfer(config.yellow);
+
+#define RECONFIGURE(r_lookup, g_lookup, b_lookup, max) \
+       for(int i = 0; i <= max; i++) \
+       { \
+               r_lookup[i] = CLIP((int)(r_scale * i), 0, max); \
+               g_lookup[i] = CLIP((int)(g_scale * i), 0, max); \
+               b_lookup[i] = CLIP((int)(b_scale * i), 0, max); \
+       }
+
+       RECONFIGURE(r_lookup_8, g_lookup_8, b_lookup_8, 0xff);
+       RECONFIGURE(r_lookup_16, g_lookup_16, b_lookup_16, 0xffff);
+       
+       return 0;
+}
+
+int64_t ColorBalanceMain::calculate_slider(float in)
+{
+       if(in < 1.0)
+       {
+               return (int64_t)(in * 1000 - 1000.0);
+       }
+       else
+       if(in > 1.0)
+       {
+               return (int64_t)(1000 * (in - 1.0) / MAX_COLOR);
+       }
+       else
+               return 0;
+}
+
+float ColorBalanceMain::calculate_transfer(float in)
+{
+       if(in < 0)
+       {
+               return (1000.0 + in) / 1000.0;
+       }
+       else
+       if(in > 0)
+       {
+               return 1.0 + in / 1000.0 * MAX_COLOR;
+       }
+       else
+               return 1.0;
+}
+
+
+
+
+int ColorBalanceMain::test_boundary(float &value)
+{
+
+       if(value < -1000) value = -1000;
+    if(value > 1000) value = 1000;
+       return 0;
+}
+
+int ColorBalanceMain::synchronize_params(ColorBalanceSlider *slider, float difference)
+{
+       if(thread && config.lock_params)
+    {
+           if(slider != ((ColorBalanceWindow*)thread->window)->cyan)
+        {
+               config.cyan += difference;
+            test_boundary(config.cyan);
+               ((ColorBalanceWindow*)thread->window)->cyan->update((int64_t)config.cyan);
+        }
+           if(slider != ((ColorBalanceWindow*)thread->window)->magenta)
+        {
+               config.magenta += difference;
+            test_boundary(config.magenta);
+               ((ColorBalanceWindow*)thread->window)->magenta->update((int64_t)config.magenta);
+        }
+           if(slider != ((ColorBalanceWindow*)thread->window)->yellow)
+        {
+               config.yellow += difference;
+            test_boundary(config.yellow);
+               ((ColorBalanceWindow*)thread->window)->yellow->update((int64_t)config.yellow);
+        }
+    }
+       return 0;
+}
+
+
+
+
+
+
+LOAD_CONFIGURATION_MACRO(ColorBalanceMain, ColorBalanceConfig)
+NEW_WINDOW_MACRO(ColorBalanceMain, ColorBalanceWindow)
+
+
+
+
+
+int ColorBalanceMain::process_buffer(VFrame *frame,
+       int64_t start_position,
+       double frame_rate)
+{
+       need_reconfigure |= load_configuration();
+
+//printf("ColorBalanceMain::process_realtime 1 %d\n", need_reconfigure);
+       if(need_reconfigure)
+       {
+               if(!engine)
+               {
+                       total_engines = PluginClient::smp + 1;
+                       engine = new ColorBalanceEngine*[total_engines];
+                       for(int i = 0; i < total_engines; i++)
+                       {
+                               engine[i] = new ColorBalanceEngine(this);
+                               engine[i]->start();
+                       }
+               }
+
+               reconfigure();
+               need_reconfigure = 0;
+       }
+
+       frame->get_params()->update("COLORBALANCE_PRESERVE", config.preserve);
+       frame->get_params()->update("COLORBALANCE_CYAN", calculate_transfer(config.cyan));
+       frame->get_params()->update("COLORBALANCE_MAGENTA", calculate_transfer(config.magenta));
+       frame->get_params()->update("COLORBALANCE_YELLOW", calculate_transfer(config.yellow));
+
+
+       read_frame(frame,
+               0,
+               get_source_position(),
+               get_framerate(),
+               get_use_opengl());
+
+       int aggregate_interpolate = 0;
+       int aggregate_gamma = 0;
+       get_aggregation(&aggregate_interpolate,
+               &aggregate_gamma);
+
+       if(!EQUIV(config.cyan, 0) || 
+               !EQUIV(config.magenta, 0) || 
+               !EQUIV(config.yellow, 0) ||
+               (get_use_opengl() &&
+                       (aggregate_interpolate ||
+                       aggregate_gamma)))
+       {
+               if(get_use_opengl())
+               {
+//get_output()->dump_stacks();
+// Aggregate
+                       if(next_effect_is(_("Histogram"))) return 0;
+                       return run_opengl();
+               }
+       
+               for(int i = 0; i < total_engines; i++)
+               {
+                       engine[i]->start_process_frame(frame, 
+                               frame, 
+                               frame->get_h() * i / total_engines, 
+                               frame->get_h() * (i + 1) / total_engines);
+               }
+
+               for(int i = 0; i < total_engines; i++)
+               {
+                       engine[i]->wait_process_frame();
+               }
+       }
+
+
+       return 0;
+}
+
+
+void ColorBalanceMain::update_gui()
+{
+       if(thread)
+       {
+               load_configuration();
+               ((ColorBalanceWindow*)thread->window)->lock_window("ColorBalanceMain::update_gui");
+               ((ColorBalanceWindow*)thread->window)->cyan->update((int64_t)config.cyan);
+               ((ColorBalanceWindow*)thread->window)->magenta->update((int64_t)config.magenta);
+               ((ColorBalanceWindow*)thread->window)->yellow->update((int64_t)config.yellow);
+               ((ColorBalanceWindow*)thread->window)->preserve->update(config.preserve);
+               ((ColorBalanceWindow*)thread->window)->lock_params->update(config.lock_params);
+               ((ColorBalanceWindow*)thread->window)->unlock_window();
+       }
+}
+
+
+
+
+void ColorBalanceMain::save_data(KeyFrame *keyframe)
+{
+       FileXML output;
+
+// cause data to be stored directly in text
+       output.set_shared_output(keyframe->get_data(), MESSAGESIZE);
+       output.tag.set_title("COLORBALANCE");
+       output.tag.set_property("CYAN", config.cyan);
+       output.tag.set_property("MAGENTA",  config.magenta);
+       output.tag.set_property("YELLOW",  config.yellow);
+       output.tag.set_property("PRESERVELUMINOSITY",  config.preserve);
+       output.tag.set_property("LOCKPARAMS",  config.lock_params);
+       output.append_tag();
+       output.tag.set_title("/COLORBALANCE");
+       output.append_tag();
+       output.append_newline();
+       output.terminate_string();
+}
+
+void ColorBalanceMain::read_data(KeyFrame *keyframe)
+{
+       FileXML input;
+
+       input.set_shared_input(keyframe->get_data(), strlen(keyframe->get_data()));
+
+       int result = 0;
+
+       while(!result)
+       {
+               result = input.read_tag();
+
+               if(!result)
+               {
+                       if(input.tag.title_is("COLORBALANCE"))
+                       {
+                               config.cyan = input.tag.get_property("CYAN", config.cyan);
+                               config.magenta = input.tag.get_property("MAGENTA", config.magenta);
+                               config.yellow = input.tag.get_property("YELLOW", config.yellow);
+                               config.preserve = input.tag.get_property("PRESERVELUMINOSITY", config.preserve);
+                               config.lock_params = input.tag.get_property("LOCKPARAMS", config.lock_params);
+                       }
+               }
+       }
+}
+
+void ColorBalanceMain::get_aggregation(int *aggregate_interpolate,
+       int *aggregate_gamma)
+{
+       if(!strcmp(get_output()->get_prev_effect(1), _("Interpolate Pixels")) &&
+               !strcmp(get_output()->get_prev_effect(0), _("Gamma")))
+       {
+               *aggregate_interpolate = 1;
+               *aggregate_gamma = 1;
+       }
+       else
+       if(!strcmp(get_output()->get_prev_effect(0), _("Interpolate Pixels")))
+       {
+               *aggregate_interpolate = 1;
+       }
+       else
+       if(!strcmp(get_output()->get_prev_effect(0), _("Gamma")))
+       {
+               *aggregate_gamma = 1;
+       }
+}
+
+int ColorBalanceMain::handle_opengl()
+{
+#ifdef HAVE_GL
+
+       get_output()->to_texture();
+       get_output()->enable_opengl();
+
+       unsigned int shader = 0;
+       const char *shader_stack[] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+       int current_shader = 0;
+       int aggregate_interpolate = 0;
+       int aggregate_gamma = 0;
+
+       get_aggregation(&aggregate_interpolate,
+               &aggregate_gamma);
+
+//printf("ColorBalanceMain::handle_opengl %d %d\n", aggregate_interpolate, aggregate_gamma);
+       if(aggregate_interpolate)
+               INTERPOLATE_COMPILE(shader_stack, current_shader)
+
+       if(aggregate_gamma)
+               GAMMA_COMPILE(shader_stack, current_shader, aggregate_interpolate)
+
+       COLORBALANCE_COMPILE(shader_stack, 
+               current_shader, 
+               aggregate_gamma || aggregate_interpolate)
+
+       shader = VFrame::make_shader(0, 
+               shader_stack[0], 
+               shader_stack[1], 
+               shader_stack[2], 
+               shader_stack[3], 
+               shader_stack[4], 
+               shader_stack[5], 
+               shader_stack[6], 
+               shader_stack[7], 
+               0);
+
+       if(shader > 0)
+       {
+               glUseProgram(shader);
+               glUniform1i(glGetUniformLocation(shader, "tex"), 0);
+
+               if(aggregate_interpolate) INTERPOLATE_UNIFORMS(shader);
+               if(aggregate_gamma) GAMMA_UNIFORMS(shader);
+
+               COLORBALANCE_UNIFORMS(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;
+}
+
+
+
+
+
+
+