/* * C41 plugin for Cinelerra * Copyright (C) 2011 Florent Delannoy * * 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 "bcdisplayinfo.h" #include "bchash.h" #include "clip.h" #include "filexml.h" #include "guicast.h" #include "language.h" #include "cicolors.h" #include "pluginvclient.h" #include "vframe.h" #include #include /* Class declarations */ class C41Effect; class C41Window; struct magic { float min_r; float min_g; float min_b; float light; float gamma_g; float gamma_b; }; class C41Config { public: C41Config(); void copy_from(C41Config &src); int equivalent(C41Config &src); void interpolate(C41Config &prev, C41Config &next, long prev_frame, long next_frame, long current_frame); int active; int compute_magic; float fix_min_r; float fix_min_g; float fix_min_b; float fix_light; float fix_gamma_g; float fix_gamma_b; }; class C41Enable : public BC_CheckBox { public: C41Enable(C41Effect *plugin, int *output, int x, int y, char *text); int handle_event(); C41Effect *plugin; int *output; }; class C41TextBox : public BC_TextBox { public: C41TextBox(C41Effect *plugin, float *value, int x, int y); int handle_event(); C41Effect *plugin; float *boxValue; }; class C41Button : public BC_GenericButton { public: C41Button(C41Effect *plugin, C41Window *window, int x, int y); int handle_event(); C41Effect *plugin; C41Window *window; float *boxValue; }; class C41Window : public PluginClientWindow { public: C41Window(C41Effect *plugin); void create_objects(); void update(); void update_magic(); C41Enable *active; C41Enable *compute_magic; BC_Title *min_r; BC_Title *min_g; BC_Title *min_b; BC_Title *light; BC_Title *gamma_g; BC_Title *gamma_b; C41TextBox *fix_min_r; C41TextBox *fix_min_g; C41TextBox *fix_min_b; C41TextBox *fix_light; C41TextBox *fix_gamma_g; C41TextBox *fix_gamma_b; C41Button *lock; C41Effect *plugin; }; class C41Effect : public PluginVClient { public: C41Effect(PluginServer *server); ~C41Effect(); PLUGIN_CLASS_MEMBERS(C41Config); int process_buffer(VFrame *frame, int64_t start_position, double frame_rate); int is_realtime(); void save_data(KeyFrame *keyframe); void read_data(KeyFrame *keyframe); void update_gui(); void render_gui(void* data); int show_gui(); void raise_window(); float myLog2(float i) __attribute__ ((optimize(0))); float myPow2(float i) __attribute__ ((optimize(0))); float myPow(float a, float b); double difftime_nano(timespec start, timespec end); struct magic values; }; REGISTER_PLUGIN(C41Effect); /* Methods decarations */ // C41Config C41Config::C41Config() { active = 0; compute_magic = 0; fix_min_r = fix_min_g = fix_min_b = fix_light = fix_gamma_g = fix_gamma_b = 0.; } void C41Config::copy_from(C41Config &src) { active = src.active; compute_magic = src.compute_magic; fix_min_r = src.fix_min_r; fix_min_g = src.fix_min_g; fix_min_b = src.fix_min_b; fix_light = src.fix_light; fix_gamma_g = src.fix_gamma_g; fix_gamma_b = src.fix_gamma_b; } int C41Config::equivalent(C41Config &src) { return (src.active == active && compute_magic == src.compute_magic && EQUIV(src.fix_min_r, fix_min_r) && EQUIV(src.fix_min_g, fix_min_g) && EQUIV(src.fix_min_b, fix_min_b) && EQUIV(src.fix_light, fix_light) && EQUIV(src.fix_gamma_g, fix_gamma_g) && EQUIV(src.fix_gamma_b, fix_gamma_b)); } void C41Config::interpolate(C41Config &prev, C41Config &next, long prev_frame, long next_frame, long current_frame) { active = prev.active; compute_magic = prev.compute_magic; fix_min_r = prev.fix_min_r; fix_min_g = prev.fix_min_g; fix_min_b = prev.fix_min_b; fix_light = prev.fix_light; fix_gamma_g = prev.fix_gamma_g; fix_gamma_b = prev.fix_gamma_b; } // C41Enable C41Enable::C41Enable(C41Effect *plugin, int *output, int x, int y, char *text) : BC_CheckBox(x, y, *output, text) { this->plugin = plugin; this->output = output; } int C41Enable::handle_event() { *output = get_value(); plugin->send_configure_change(); return 1; } // C41TextBox C41TextBox::C41TextBox(C41Effect *plugin, float *value, int x, int y) : BC_TextBox(x, y, 160, 1, *value) { this->plugin = plugin; this->boxValue = value; } int C41TextBox::handle_event() { *boxValue = atof(get_text()); plugin->send_configure_change(); return 1; } // C41Button C41Button::C41Button(C41Effect *plugin, C41Window *window, int x, int y) : BC_GenericButton(x, y, _("Lock parameters")) { this->plugin = plugin; this->window = window; } int C41Button::handle_event() { plugin->config.fix_min_r = plugin->values.min_r; plugin->config.fix_min_g = plugin->values.min_g; plugin->config.fix_min_b = plugin->values.min_b; plugin->config.fix_light = plugin->values.light; plugin->config.fix_gamma_g = plugin->values.gamma_g; plugin->config.fix_gamma_b = plugin->values.gamma_b; window->update(); plugin->send_configure_change(); return 1; } // C41Window C41Window::C41Window(C41Effect *plugin) : PluginClientWindow(plugin, 270, 620, 1, 0, 1) { this->plugin = plugin; } void C41Window::create_objects() { int x = 10; int y = 10; add_subwindow(active = new C41Enable(plugin, &plugin->config.active, x, y, _("Activate processing"))); y += 40; add_subwindow(compute_magic = new C41Enable(plugin, &plugin->config.compute_magic, x, y, _("Compute negfix values"))); y += 20; add_subwindow(new BC_Title(x + 20, y, _("(uncheck for faster rendering)"))); y += 40; add_subwindow(new BC_Title(x, y, _("Computed negfix values:"))); y += 30; add_subwindow(new BC_Title(x, y, _("Min R:"))); add_subwindow(min_r = new BC_Title(x + 80, y, "0.0000")); y += 30; add_subwindow(new BC_Title(x, y, _("Min G:"))); add_subwindow(min_g = new BC_Title(x + 80, y, "0.0000")); y += 30; add_subwindow(new BC_Title(x, y, _("Min B:"))); add_subwindow(min_b = new BC_Title(x + 80, y, "0.0000")); y += 30; add_subwindow(new BC_Title(x, y, _("Light:"))); add_subwindow(light = new BC_Title(x + 80, y, "0.0000")); y += 30; add_subwindow(new BC_Title(x, y, _("Gamma G:"))); add_subwindow(gamma_g = new BC_Title(x + 80, y, "0.0000")); y += 30; add_subwindow(new BC_Title(x, y, _("Gamma B:"))); add_subwindow(gamma_b = new BC_Title(x + 80, y, "0.0000")); y += 30; add_subwindow(lock = new C41Button(plugin, this, x, y)); y += 30; y += 20; add_subwindow(new BC_Title(x, y, _("negfix values to apply:"))); y += 30; add_subwindow(new BC_Title(x, y, _("Min R:"))); add_subwindow(fix_min_r = new C41TextBox(plugin, &plugin->config.fix_min_r, x + 80, y)); y += 30; add_subwindow(new BC_Title(x, y, _("Min G:"))); add_subwindow(fix_min_g = new C41TextBox(plugin, &plugin->config.fix_min_g, x + 80, y)); y += 30; add_subwindow(new BC_Title(x, y, _("Min B:"))); add_subwindow(fix_min_b = new C41TextBox(plugin, &plugin->config.fix_min_b, x + 80, y)); y += 30; add_subwindow(new BC_Title(x, y, _("Light:"))); add_subwindow(fix_light = new C41TextBox(plugin, &plugin->config.fix_light, x + 80, y)); y += 30; add_subwindow(new BC_Title(x, y, _("Gamma G:"))); add_subwindow(fix_gamma_g = new C41TextBox(plugin, &plugin->config.fix_gamma_g, x + 80, y)); y += 30; add_subwindow(new BC_Title(x, y, _("Gamma B:"))); add_subwindow(fix_gamma_b = new C41TextBox(plugin, &plugin->config.fix_gamma_b, x + 80, y)); y += 30; show_window(); flush(); update_magic(); } void C41Window::update() { active->update(plugin->config.active); compute_magic->update(plugin->config.compute_magic); fix_min_r->update(plugin->config.fix_min_r); fix_min_g->update(plugin->config.fix_min_g); fix_min_b->update(plugin->config.fix_min_b); fix_light->update(plugin->config.fix_light); fix_gamma_g->update(plugin->config.fix_gamma_g); fix_gamma_b->update(plugin->config.fix_gamma_b); update_magic(); } void C41Window::update_magic() { min_r->update(plugin->values.min_r); min_g->update(plugin->values.min_g); min_b->update(plugin->values.min_b); light->update(plugin->values.light); gamma_g->update(plugin->values.gamma_g); gamma_b->update(plugin->values.gamma_b); } // C41Effect C41Effect::C41Effect(PluginServer *server) : PluginVClient(server) { memset(&values, 0, sizeof(values)); } C41Effect::~C41Effect() { } const char* C41Effect::plugin_title() { return _("C41"); } int C41Effect::is_realtime() { return 1; } LOAD_CONFIGURATION_MACRO(C41Effect, C41Config) NEW_WINDOW_MACRO(C41Effect, C41Window) void C41Effect::update_gui() { if(thread && load_configuration()) { C41Window *window = (C41Window *)thread->window; window->lock_window("C41Effect::update_gui"); window->update(); window->unlock_window(); } } void C41Effect::render_gui(void* data) { // Updating values computed by process_frame struct magic *vp = (struct magic *)data; values = *vp; if(thread) { C41Window *window = (C41Window *)thread->window; window->update_magic(); } } void C41Effect::save_data(KeyFrame *keyframe) { FileXML output; output.set_shared_output(keyframe->get_data(), MESSAGESIZE); output.tag.set_title("C41"); output.tag.set_property("ACTIVE", config.active); output.tag.set_property("COMPUTE_MAGIC", config.compute_magic); output.tag.set_property("FIX_MIN_R", config.fix_min_r); output.tag.set_property("FIX_MIN_G", config.fix_min_g); output.tag.set_property("FIX_MIN_B", config.fix_min_b); output.tag.set_property("FIX_LIGHT", config.fix_light); output.tag.set_property("FIX_GAMMA_G", config.fix_gamma_g); output.tag.set_property("FIX_GAMMA_B", config.fix_gamma_b); output.append_tag(); output.tag.set_title("/C41"); output.append_tag(); output.terminate_string(); } void C41Effect::read_data(KeyFrame *keyframe) { FileXML input; input.set_shared_input(keyframe->get_data(), strlen(keyframe->get_data())); while(!input.read_tag()) { if(input.tag.title_is("C41")) { config.active = input.tag.get_property("ACTIVE", config.active); config.compute_magic = input.tag.get_property("COMPUTE_MAGIC", config.compute_magic); config.fix_min_r = input.tag.get_property("FIX_MIN_R", config.fix_min_r); config.fix_min_g = input.tag.get_property("FIX_MIN_G", config.fix_min_g); config.fix_min_b = input.tag.get_property("FIX_MIN_B", config.fix_min_b); config.fix_light = input.tag.get_property("FIX_LIGHT", config.fix_light); config.fix_gamma_g = input.tag.get_property("FIX_GAMMA_G", config.fix_gamma_g); config.fix_gamma_b = input.tag.get_property("FIX_GAMMA_B", config.fix_gamma_b); } } } /* Faster pow() approximation; borrowed from http://www.dctsystems.co.uk/Software/power.html * Tests on real-world data showed a max error of 4% and avg. error or .1 to .5%, * while accelerating rendering by a factor of 4. */ float C41Effect::myLog2(float i) { float x; float y; float LogBodge = 0.346607f; x = *(int *)&i; x *= 1.0 / (1 << 23); // 1/pow(2,23); x = x - 127; y = x - floorf(x); y = (y - y * y) * LogBodge; return x + y; } float C41Effect::myPow2(float i) { float PowBodge = 0.33971f; float x; float y = i - floorf(i); y = (y - y * y) * PowBodge; x = i + 127 - y; x *= (1 << 23); *(int*) &x = (int)x; return x; } float C41Effect::myPow(float a, float b) { return myPow2(b * myLog2(a)); } int C41Effect::process_buffer(VFrame *vframe, int64_t start_position, double frame_rate) { load_configuration(); VFrame *frame = vframe; read_frame(frame, 0, start_position, frame_rate, 0); int frame_w = frame->get_w(); int frame_h = frame->get_h(); int color_model = frame->get_color_model(); int active_model = BC_CModels::has_alpha(color_model) ? BC_RGBA_FLOAT : BC_RGB_FLOAT; int components = active_model == BC_RGBA_FLOAT ? 4 : 3; if( color_model != active_model ) { new_temp(frame_w, frame_h, active_model); frame = get_temp(); frame->transfer_from(vframe); } if(config.compute_magic) { // Box blur! VFrame* tmp_frame = new VFrame(*frame); VFrame* blurry_frame = new VFrame(*frame); float** rows = (float**)frame->get_rows(); float** tmp_rows = (float**)tmp_frame->get_rows(); float** blurry_rows = (float**)blurry_frame->get_rows(); for(int i = 0; i < frame_h; i++) for(int j = 0; j < (components*frame_w); j++) blurry_rows[i][j] = rows[i][j]; int boxw = 5, boxh = 5; // 10 passes of Box blur should be good int pass, x, y, y_up, y_down, x_right, x_left; float component; for(pass=0; pass<10; pass++) { for(y = 0; y < frame_h; y++) for(x = 0; x < (components * frame_w); x++) tmp_rows[y][x] = blurry_rows[y][x]; for(y = 0; y < frame_h; y++) { y_up = (y - boxh < 0)? 0 : y - boxh; y_down = (y + boxh >= frame_h)? frame_h - 1 : y + boxh; for(x = 0; x < (components*frame_w); x++) { x_left = (x-(components*boxw) < 0)? 0 : x-(components*boxw); x_right = (x+(components*boxw) >= (components*frame_w)) ? (components*frame_w)-1 : x+(components*boxw); component = (tmp_rows[y_down][x_right] +tmp_rows[y_up][x_left] +tmp_rows[y_up][x_right] +tmp_rows[y_down][x_right])/4; blurry_rows[y][x] = component; } } } // Compute magic negfix values float minima_r = 50., minima_g = 50., minima_b = 50.; float maxima_r = 0., maxima_g = 0., maxima_b = 0.; // Shave the image in order to avoid black borders // Tolerance default: 5%, i.e. 0.05 #define TOLERANCE 0.20 #define SKIP_ROW if (i < (TOLERANCE * frame_h) || i > ((1-TOLERANCE)*frame_h)) continue #define SKIP_COL if (j < (TOLERANCE * frame_w) || j > ((1-TOLERANCE)*frame_w)) continue for(int i = 0; i < frame_h; i++) { SKIP_ROW; float *row = (float*)blurry_frame->get_rows()[i]; for(int j = 0; j < frame_w; j++, row += components) { SKIP_COL; if(row[0] < minima_r) minima_r = row[0]; if(row[0] > maxima_r) maxima_r = row[0]; if(row[1] < minima_g) minima_g = row[1]; if(row[1] > maxima_g) maxima_g = row[1]; if(row[2] < minima_b) minima_b = row[2]; if(row[2] > maxima_b) maxima_b = row[2]; } } // Delete the VFrames we used for blurring delete tmp_frame; delete blurry_frame; if( minima_r < 1e-3) minima_r = 1e-3; if( minima_g < 1e-3) minima_g = 1e-3; if( minima_b < 1e-3) minima_b = 1e-3; values.min_r = minima_r; values.min_g = minima_g; values.min_b = minima_b; values.light = (minima_r / maxima_r) * 0.95; values.gamma_g = fabs(maxima_g - minima_g) < 1e-3 ? 1. : logf(maxima_r / minima_r) / logf(maxima_g / minima_g); values.gamma_b = fabs(maxima_b - minima_b) < 1e-3 ? 1. : logf(maxima_r / minima_r) / logf(maxima_b / minima_b); // Update GUI send_render_gui(&values); } // Apply the transformation if(config.active) { // Get the values from the config instead of the computed ones for(int i = 0; i < frame_h; i++) { float *row = (float*)frame->get_rows()[i]; for(int j = 0; j < frame_w; j++, row += components) { row[0] = row[0] < 1e-3 ? 1.f : (config.fix_min_r / row[0]) - config.fix_light; row[1] = row[1] < 1e-3 ? 1.f : myPow((config.fix_min_g / row[1]), config.fix_gamma_g) - config.fix_light; row[2] = row[2] < 1e-3 ? 1.f : myPow((config.fix_min_b / row[2]), config.fix_gamma_b) - config.fix_light; } } } if( vframe != frame ) vframe->transfer_from(frame); return 0; }