--- /dev/null
+/*
+ * 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;
+}
+
+
+
+
+