Credit SGE conversion of Adam-s plugins ChromakeyAvid/Color Swatch + updated ContextM...
[goodguy/cinelerra.git] / cinelerra-5.1 / plugins / swatch / swatch.C
diff --git a/cinelerra-5.1/plugins/swatch/swatch.C b/cinelerra-5.1/plugins/swatch/swatch.C
new file mode 100644 (file)
index 0000000..2e9142e
--- /dev/null
@@ -0,0 +1,760 @@
+/*
+ * CINELERRA
+ * Copyright (C) 2024 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
+ * 
+ */
+
+
+// draw a color swatch for a given brightness or saturation
+// does not visualize but draws output to be processed
+
+#include <math.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "bcdisplayinfo.h"
+#include "bccolors.h"
+#include "clip.h"
+#include "bchash.h"
+#include "filexml.h"
+#include "keyframe.h"
+#include "language.h"
+#include "playback3d.h"
+#include "swatch.h"
+#include "vframe.h"
+
+
+
+
+REGISTER_PLUGIN(SwatchMain)
+
+
+#define MAX_VALUE 100
+
+
+
+SwatchConfig::SwatchConfig()
+{
+       brightness = MAX_VALUE;
+    saturation = MAX_VALUE;
+    fix_brightness = 0;
+    angle = 0;
+    draw_src = 0;
+}
+
+int SwatchConfig::equivalent(SwatchConfig &that)
+{
+       return brightness == that.brightness &&
+        saturation == that.saturation &&
+        fix_brightness == that.fix_brightness &&
+        angle == that.angle &&
+        draw_src == that.draw_src;
+}
+
+void SwatchConfig::copy_from(SwatchConfig &that)
+{
+       brightness = that.brightness;
+       saturation = that.saturation;
+    fix_brightness = that.fix_brightness;
+    angle = that.angle;
+    draw_src = that.draw_src;
+}
+
+void SwatchConfig::interpolate(SwatchConfig &prev, 
+       SwatchConfig &next, 
+       long prev_frame, 
+       long next_frame, 
+       long 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->brightness = (int)(prev.brightness * prev_scale + next.brightness * next_scale);
+       this->saturation = (int)(prev.saturation * prev_scale + next.saturation * next_scale);
+       this->angle = (int)(prev.angle * prev_scale + next.angle * next_scale);
+    fix_brightness = prev.fix_brightness;
+    draw_src = prev.draw_src;
+}
+
+
+
+
+SwatchSlider::SwatchSlider(SwatchMain *plugin, 
+                          SwatchWindow *gui,
+                          int x, 
+                          int y,
+                          int min,
+                          int max,
+                          int *output)
+  : BC_ISlider(x,
+              y,
+              0, 
+              gui->get_w() - xS(10) - x, 
+              gui->get_w() - xS(10) - x, 
+              min, 
+              max, 
+              *output)
+{
+  this->plugin = plugin;
+  this->output = output;
+}
+
+int SwatchSlider::handle_event ()
+{
+       *output = get_value();
+       plugin->send_configure_change();
+       return 1;
+}
+
+SwatchRadial::SwatchRadial(SwatchMain *plugin, 
+    SwatchWindow *gui, 
+    int x, 
+    int y, 
+    const char *text,
+    int fix_brightness)
+ : BC_Radial(x, 
+       y, 
+       (plugin->config.fix_brightness && fix_brightness), 
+       text)
+{
+       this->plugin = plugin;
+    this->gui = gui;
+    this->fix_brightness = fix_brightness;
+}
+
+int SwatchRadial::handle_event()
+{
+    plugin->config.fix_brightness = fix_brightness;
+    gui->update_fixed();
+    plugin->send_configure_change();
+    return 1;
+}
+
+
+SwatchCheck::SwatchCheck(SwatchMain *plugin, 
+    int x, 
+    int y, 
+    const char *text,
+    int *output)
+ : BC_CheckBox(x, 
+       y, 
+       *output, 
+       text)
+{
+       this->plugin = plugin;
+    this->output = output;
+}
+
+int SwatchCheck::handle_event()
+{
+    *output = get_value();
+    plugin->send_configure_change();
+    return 1;
+}
+
+
+
+
+SwatchWindow::SwatchWindow(SwatchMain *plugin)
+ : PluginClientWindow(plugin,
+       xS(350), 
+       yS(300), 
+       xS(350), 
+       yS(300), 
+       0)
+{
+       this->plugin = plugin;
+}
+
+SwatchWindow::~SwatchWindow()
+{
+}
+
+void SwatchWindow::create_objects()
+{
+       int margin = yS(10);
+       int x = margin, y = margin;
+       BC_Title *title;
+
+       add_subwindow(brightness_title = new BC_Title(x, y, _("Brightness:")));
+    y += brightness_title->get_h() + margin;
+       add_subwindow (brightness = new SwatchSlider(plugin, this, x, y, 0, MAX_VALUE, &plugin->config.brightness));
+    y += brightness->get_h() + margin;
+
+       add_subwindow(saturation_title = new BC_Title(x, y, _("Saturation:")));
+    y += saturation_title->get_h() + margin;
+       add_subwindow (saturation = new SwatchSlider(plugin, this, x, y, 0, MAX_VALUE, &plugin->config.saturation));
+    y += saturation->get_h() + margin;
+
+       add_subwindow(title = new BC_Title(x, y, _("Angle:")));
+    y += title->get_h() + margin;
+       add_subwindow (angle = new SwatchSlider(plugin, this, x, y, -180, 180, &plugin->config.angle));
+    y += saturation->get_h() + margin;
+
+    add_subwindow(fix_brightness = new SwatchRadial(plugin, 
+        this, 
+        x, 
+        y, 
+        _("Constant Brightness"),
+        1));
+    y += fix_brightness->get_h() + margin;
+    add_subwindow(fix_saturation = new SwatchRadial(plugin, 
+        this, 
+        x, 
+        y, 
+        _("Constant Saturation"),
+        0));
+    y += fix_saturation->get_h() + margin;
+    update_fixed();
+    
+    add_subwindow(draw_src = new SwatchCheck(plugin, 
+        x, 
+        y, 
+        _("Draw source"),
+        &plugin->config.draw_src));
+   
+    
+       show_window();
+}
+
+void SwatchWindow::update_fixed()
+{
+    fix_brightness->update(plugin->config.fix_brightness);
+    fix_saturation->update(!plugin->config.fix_brightness);
+    if(plugin->config.fix_brightness)
+    {
+        saturation_title->set_color(BC_WindowBase::get_resources()->disabled_text_color);
+        brightness_title->set_color(BC_WindowBase::get_resources()->default_text_color);
+        saturation->disable();
+        brightness->enable();
+    }
+    else
+    {
+        saturation_title->set_color(BC_WindowBase::get_resources()->default_text_color);
+        brightness_title->set_color(BC_WindowBase::get_resources()->disabled_text_color);
+        saturation->enable();
+        brightness->disable();
+    }
+}
+
+
+SwatchMain::SwatchMain(PluginServer *server)
+ : PluginVClient(server)
+{
+       need_reconfigure = 1;
+       engine = 0;
+    temp = 0;
+    src_temp = 0;
+}
+
+SwatchMain::~SwatchMain()
+{
+       if(engine) delete engine;
+    if(temp) delete temp;
+    if(src_temp) delete src_temp;
+}
+
+const char* SwatchMain::plugin_title() { return N_("Color Swatch"); }
+int SwatchMain::is_realtime() { return 1; }
+
+
+NEW_WINDOW_MACRO(SwatchMain, SwatchWindow)
+
+LOAD_CONFIGURATION_MACRO(SwatchMain, SwatchConfig)
+
+int SwatchMain::is_synthesis()
+{
+       return 1;
+}
+
+int SwatchMain::process_buffer(VFrame *frame,
+       int64_t start_position,
+       double frame_rate)
+{
+       need_reconfigure |= load_configuration();
+       int use_opengl = get_use_opengl();
+
+// have to draw output pixels out of order
+    if(config.draw_src) use_opengl = 0;
+
+       if(use_opengl) return run_opengl();
+
+       if(!engine) engine = new SwatchEngine(this,
+               get_project_smp() + 1,
+               get_project_smp() + 1);
+    if(config.draw_src)
+    {
+        if(!src_temp)
+            src_temp = new VFrame(0, 
+                       -1,
+                       frame->get_w(),
+                       frame->get_h(),
+                   frame->get_color_model(),
+                       -1);
+
+        read_frame(src_temp, 
+                   0, 
+                   start_position, 
+                   frame_rate,
+                   use_opengl);
+    }
+
+
+       if(!temp) temp = new VFrame(0, 
+               -1,
+               frame->get_w(),
+               frame->get_h(),
+           frame->get_color_model(),
+               -1);
+
+// draw pattern once
+    if(need_reconfigure)
+           engine->draw_pattern();
+
+// draw the pattern on the output
+    frame->copy_from(temp);
+// draw input on the pattern
+    if(config.draw_src)
+        engine->draw_src();
+
+//printf("SwatchMain::process_buffer %d %d\n", __LINE__, config.draw_src);
+       return 0;
+}
+
+
+void SwatchMain::update_gui()
+{
+       if(thread)
+       {
+               if(load_configuration())
+               {
+                       ((SwatchWindow*)thread->window)->lock_window("SwatchMain::update_gui");
+                       ((SwatchWindow*)thread->window)->brightness->update(config.brightness);
+                       ((SwatchWindow*)thread->window)->saturation->update(config.saturation);
+                       ((SwatchWindow*)thread->window)->angle->update(config.angle);
+                       ((SwatchWindow*)thread->window)->draw_src->update(config.draw_src);
+                       ((SwatchWindow*)thread->window)->update_fixed();
+                       ((SwatchWindow*)thread->window)->unlock_window();
+               }
+       }
+}
+
+
+
+
+
+void SwatchMain::save_data(KeyFrame *keyframe)
+{
+       FileXML output;
+
+// cause data to be stored directly in text
+       output.set_shared_output(keyframe->xbuf);
+       output.tag.set_title("SWATCH");
+
+       output.tag.set_property("BRIGHTNESS", config.brightness);
+       output.tag.set_property("SATURATION", config.saturation);
+       output.tag.set_property("ANGLE", config.angle);
+       output.tag.set_property("FIX_BRIGHTNESS", config.fix_brightness);
+       output.tag.set_property("DRAW_SRC", config.draw_src);
+       output.append_tag();
+       output.tag.set_title("/SWATCH");
+       output.append_tag();
+       output.append_newline();
+       output.terminate_string();
+//printf("SwatchMain::save_data %d %s\n", __LINE__, output.string);
+}
+
+void SwatchMain::read_data(KeyFrame *keyframe)
+{
+  FileXML input;
+
+  input.set_shared_input(keyframe->xbuf);
+
+  while( !input.read_tag() )
+  {
+    if(input.tag.title_is("SWATCH"))
+    {
+      config.brightness =
+       input.tag.get_property("BRIGHTNESS", config.brightness);
+      config.saturation =
+       input.tag.get_property("SATURATION", config.saturation);
+      config.angle =
+       input.tag.get_property("ANGLE", config.angle);
+      config.fix_brightness =
+       input.tag.get_property("FIX_BRIGHTNESS", config.fix_brightness);
+      config.draw_src =
+       input.tag.get_property("DRAW_SRC", config.draw_src);
+    }
+  }
+}
+
+int SwatchMain::handle_opengl()
+{
+#ifdef HAVE_GL
+       const char *head_frag =
+               "uniform mat3 yuv_to_rgb_matrix;\n"
+               "uniform mat3 rgb_to_yuv_matrix;\n"
+               "uniform float yminf;\n"
+               "uniform sampler2D tex;\n"
+               "uniform vec2 texture_extents;\n"
+               "uniform vec2 center_coord;\n"
+               "uniform float value;\n"
+               "uniform float saturation;\n"
+               "uniform float angle;\n"
+               "uniform bool fix_value;\n"
+               "\n"
+               "void main()\n"
+               "{\n"
+               "       vec2 out_coord = gl_TexCoord[0].st * texture_extents;\n"
+               "       vec2 in_coord = vec2(out_coord.x - center_coord.x, out_coord.y - center_coord.y);\n"
+        "   float max_s = center_coord.x;\n"
+        "   if(center_coord.y < max_s) max_s = center_coord.y;\n"
+        "   vec4 pixel;\n"
+        "   pixel.a = 1.0;\n"
+        "   pixel.r = atan(in_coord.x, in_coord.y) / 2.0 / 3.14159 * 360.0 + angle; // hue\n"
+        "   if(pixel.r < 0.0) pixel.r += 360.0;\n"
+        "   if(fix_value)\n"
+        "   {\n"
+        "       pixel.g = length(vec2(in_coord.x, in_coord.y)) / max_s; // saturation\n"
+        "       if(pixel.g > 1.0) pixel.g = 1.0; \n"
+        "       pixel.b = value;\n"
+        "   }\n"
+        "   else\n"
+        "   {\n"
+        "       pixel.g = saturation;\n"
+        "       pixel.b = length(vec2(in_coord.x, in_coord.y)) / max_s; // value\n"
+        "       if(pixel.b > 1.0) pixel.b = 1.0; \n"
+        "   }\n"
+        HSV_TO_RGB_FRAG("pixel");
+
+       static const char *put_yuv_frag =
+                       RGB_TO_YUV_FRAG("pixel")
+               "       gl_FragColor = pixel;\n"
+               "}\n";
+
+       static const char *put_rgb_frag =
+               "       gl_FragColor = pixel;\n"
+               "}\n";
+
+
+
+
+       const char *shader_stack[5] = { 0, 0, 0, 0, 0 };
+       shader_stack[0] = head_frag;
+       if(BC_CModels::is_yuv(get_output()->get_color_model()))
+         shader_stack[1] = put_yuv_frag;
+       else
+         shader_stack[1] = put_rgb_frag;
+
+       get_output()->set_opengl_state(VFrame::TEXTURE);
+       get_output()->to_texture();
+       get_output()->enable_opengl();
+       get_output()->init_screen();
+       get_output()->bind_texture(0);
+
+       unsigned int frag = VFrame::make_shader(0, 
+               shader_stack[0], 
+               shader_stack[1], 
+               0);
+
+       if(frag)
+       {
+               glUseProgram(frag);
+               float w = get_output()->get_w();
+               float h = get_output()->get_h();
+               float texture_w = get_output()->get_texture_w();
+               float texture_h = get_output()->get_texture_h();
+               glUniform1i(glGetUniformLocation(frag, "tex"), 0);
+               glUniform2f(glGetUniformLocation(frag, "center_coord"), 
+                               (GLfloat)w / 2,
+                               (GLfloat)h / 2);
+               glUniform2f(glGetUniformLocation(frag, "texture_extents"), 
+                               (GLfloat)texture_w,
+                               (GLfloat)texture_h);
+
+               glUniform1f(glGetUniformLocation(frag, "value"), (float)config.brightness / MAX_VALUE);
+               glUniform1f(glGetUniformLocation(frag, "saturation"), (float)config.saturation / MAX_VALUE);
+               glUniform1f(glGetUniformLocation(frag, "angle"), (float)config.angle);
+               glUniform1i(glGetUniformLocation(frag, "fix_value"), config.fix_brightness);
+               if(BC_CModels::is_yuv(get_output()->get_color_model()))
+                       BC_GL_COLORS(frag);
+       }
+
+       get_output()->draw_texture();
+       glUseProgram(0);
+       get_output()->set_opengl_state(VFrame::SCREEN);
+       
+#endif
+    return 0;
+}
+
+
+
+
+
+
+
+
+
+
+
+SwatchPackage::SwatchPackage()
+ : LoadPackage()
+{
+}
+
+
+
+
+SwatchUnit::SwatchUnit(SwatchEngine *server, SwatchMain *plugin)
+ : LoadClient(server)
+{
+       this->plugin = plugin;
+       this->server = server;
+}
+
+
+
+
+#define CREATE_SWATCH(type, components, max, is_yuv) \
+{ \
+       for(int i = pkg->y1; i < pkg->y2; i++) \
+       { \
+               type *out_row = (type*)plugin->temp->get_rows()[i]; \
+        for(int j = 0; j < w; j++) \
+        { \
+            float hue = atan2(j - center_x, i - center_y) * 360 / 2 / M_PI + angle; \
+            if(fix_brightness) \
+            { \
+                saturation = hypot(j - center_x, i - center_y) / max_s; \
+                if(saturation > 1) saturation = 1; \
+            } \
+            else \
+            { \
+                value = hypot(j - center_x, i - center_y) / max_s; \
+                if(value > 1) value = 1; \
+            } \
+            if(hue < 0) hue += 360; \
+            if(is_yuv) \
+            { \
+                int y, u, v; \
+                HSV::hsv_to_yuv(y, u, v, hue, saturation, value, max); \
+                out_row[0] = y; \
+                out_row[1] = u; \
+                out_row[2] = v; \
+            } \
+            else \
+            { \
+                float r, g, b; \
+                HSV::hsv_to_rgb(r, g, b, hue, saturation, value); \
+                out_row[0] = (type)(r * max); \
+                out_row[1] = (type)(g * max); \
+                out_row[2] = (type)(b * max); \
+            } \
+ \
+/* the alpha */ \
+            if(components == 4) out_row[3] = max; \
+            out_row += components; \
+        } \
+    } \
+}
+
+
+#define DRAW_SRC(type, components, max, is_yuv) \
+{ \
+    type **dst_rows = (type**)plugin->get_output()->get_rows(); \
+       for(int i = pkg->y1; i < pkg->y2; i++) \
+       { \
+               type *src_row = (type*)plugin->src_temp->get_rows()[i]; \
+        for(int j = 0; j < w; j++) \
+        { \
+/* the source values */ \
+            type r, g, b; \
+            float r2, g2, b2; \
+            if(is_yuv) \
+            { \
+               YUV::yuv.yuv_to_rgb_f (r2, g2, b2, src_row[0], src_row[1], src_row[2]); \
+            } \
+            else \
+            { \
+                r = src_row[0]; \
+                g = src_row[1]; \
+                b = src_row[2]; \
+                r2 = (float)r / max; \
+                g2 = (float)g / max; \
+                b2 = (float)b / max; \
+            } \
+            float hue, s, value; \
+            HSV::rgb_to_hsv(r2, g2, b2, hue, s, value); \
+            float h_rad = TO_RAD(hue - angle); \
+/* get coordinate of color in output */ \
+            int x_out, y_out; \
+            if(fix_brightness) \
+            { \
+                x_out = center_x + (int)(sin(h_rad) * s * max_s); \
+                y_out = center_y + (int)(cos(h_rad) * s * max_s); \
+            } \
+            else \
+            { \
+                x_out = center_x + (int)(sin(h_rad) * value * max_s); \
+                y_out = center_y + (int)(cos(h_rad) * value * max_s); \
+            } \
+            if(x_out >= 0 && x_out < w && y_out >= 0 && y_out < h) \
+            { \
+                type *dst = dst_rows[y_out] + x_out * components; \
+                if(is_yuv) \
+                { \
+                    dst[0] = src_row[0]; \
+                    dst[1] = src_row[1]; \
+                    dst[2] = src_row[2]; \
+                } \
+                else \
+                { \
+                    dst[0] = r; \
+                    dst[1] = g; \
+                    dst[2] = b; \
+                } \
+            } \
+ \
+            src_row += components; \
+        } \
+    } \
+}
+
+#define DRAW_PATTERN_MODE 0
+#define DRAW_SRC_MODE 1
+
+void SwatchUnit::process_package(LoadPackage *package)
+{
+       SwatchPackage *pkg = (SwatchPackage*)package;
+       int h = plugin->temp->get_h();
+       int w = plugin->temp->get_w();
+       int center_x = w / 2;
+       int center_y = h / 2;
+       int cmodel = plugin->temp->get_color_model();
+// maximum saturation
+    float max_s = center_x;
+    if(center_y < max_s) max_s = center_y;
+    int fix_brightness = plugin->config.fix_brightness;
+    float saturation = (float)plugin->config.saturation / MAX_VALUE;
+    float value = (float)plugin->config.brightness / MAX_VALUE;
+    float angle = plugin->config.angle;
+
+    if(server->mode == DRAW_PATTERN_MODE)
+    {
+           switch(cmodel)
+           {
+                   case BC_RGB888:
+                           CREATE_SWATCH(unsigned char, 3, 0xff, 0)
+                           break;
+                   case BC_RGBA8888:
+                           CREATE_SWATCH(unsigned char, 4, 0xff, 0)
+                           break;
+                   case BC_RGB_FLOAT:
+                           CREATE_SWATCH(float, 3, 1.0, 0)
+                           break;
+                   case BC_RGBA_FLOAT:
+                           CREATE_SWATCH(float, 4, 1.0, 0)
+                           break;
+                   case BC_YUV888:
+                           CREATE_SWATCH(unsigned char, 3, 0xff, 1)
+                           break;
+                   case BC_YUVA8888:
+                           CREATE_SWATCH(unsigned char, 4, 0xff, 1)
+                           break;
+           }
+    }
+    else
+    {
+           switch(cmodel)
+           {
+                   case BC_RGB888:
+                           DRAW_SRC(unsigned char, 3, 0xff, 0)
+                           break;
+                   case BC_RGBA8888:
+                           DRAW_SRC(unsigned char, 4, 0xff, 0)
+                           break;
+                   case BC_RGB_FLOAT:
+                           DRAW_SRC(float, 3, 1.0, 0)
+                           break;
+                   case BC_RGBA_FLOAT:
+                           DRAW_SRC(float, 4, 1.0, 0)
+                           break;
+                   case BC_YUV888:
+                           DRAW_SRC(unsigned char, 3, 0xff, 1)
+                           break;
+                   case BC_YUVA8888:
+                           DRAW_SRC(unsigned char, 4, 0xff, 1)
+                           break;
+           }
+    }
+}
+
+
+
+
+
+
+SwatchEngine::SwatchEngine(SwatchMain *plugin, 
+       int total_clients, 
+       int total_packages)
+ : LoadServer(total_clients, total_packages)
+{
+       this->plugin = plugin;
+}
+
+void SwatchEngine::draw_pattern()
+{
+    mode = DRAW_PATTERN_MODE;
+    process_packages();
+}
+
+void SwatchEngine::draw_src()
+{
+    mode = DRAW_SRC_MODE;
+    process_packages();
+}
+
+void SwatchEngine::init_packages()
+{
+       for(int i = 0; i < get_total_packages(); i++)
+       {
+               SwatchPackage *package = (SwatchPackage*)get_package(i);
+               package->y1 = plugin->temp->get_h() * 
+                       i / 
+                       get_total_packages();
+               package->y2 = plugin->temp->get_h() * 
+                       (i + 1) /
+                       get_total_packages();
+       }
+}
+
+LoadClient* SwatchEngine::new_client()
+{
+       return new SwatchUnit(this, plugin);
+}
+
+LoadPackage* SwatchEngine::new_package()
+{
+       return new SwatchPackage;
+}
+
+
+
+
+