/* * 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 "bccolors.h" #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 #include // 1000 corresponds to (1.0 + MAX_COLOR) * input #define MAX_COLOR 1.0 REGISTER_PLUGIN(ColorBalanceMain) ColorBalanceConfig::ColorBalanceConfig() { reset(RESET_ALL); } void ColorBalanceConfig::reset(int clear) { switch(clear) { case RESET_CYAN : cyan = 0; break; case RESET_MAGENTA : magenta = 0; break; case RESET_YELLOW : yellow = 0; break; case RESET_ALL : case RESET_DEFAULT_SETTINGS : default: cyan = 0; magenta = 0; yellow = 0; lock_params = 0; preserve = 0; break; } } 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.yuv_to_rgb_8, YUV::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.yuv_to_rgb_8, YUV::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.yuv_to_rgb_8, YUV::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.yuv_to_rgb_8, YUV::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.yuv_to_rgb_16, YUV::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.yuv_to_rgb_16, YUV::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 N_("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->xbuf); 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->xbuf); 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(); const char *shader_stack[16]; memset(shader_stack,0, sizeof(shader_stack)); int current_shader = 0; int need_color_matrix = BC_CModels::is_yuv(get_output()->get_color_model()) ? 1 : 0; if( need_color_matrix ) shader_stack[current_shader++] = bc_gl_colors; 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_stack[current_shader] = 0; unsigned int shader = VFrame::make_shader(shader_stack); 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); if( need_color_matrix ) BC_GL_COLORS(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; }