2 * C41 plugin for Cinelerra
3 * Copyright (C) 2011 Florent Delannoy <florent at plui dot es>
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 #include "bcdisplayinfo.h"
28 #include "pluginvclient.h"
34 /* Class declarations */
53 void copy_from(C41Config &src);
54 int equivalent(C41Config &src);
55 void interpolate(C41Config &prev,
71 class C41Enable : public BC_CheckBox
74 C41Enable(C41Effect *plugin, int *output, int x, int y, char *text);
80 class C41TextBox : public BC_TextBox
83 C41TextBox(C41Effect *plugin, float *value, int x, int y);
89 class C41Button : public BC_GenericButton
92 C41Button(C41Effect *plugin, C41Window *window, int x, int y);
99 class C41Window : public PluginClientWindow
102 C41Window(C41Effect *plugin);
103 void create_objects();
107 C41Enable *compute_magic;
114 C41TextBox *fix_min_r;
115 C41TextBox *fix_min_g;
116 C41TextBox *fix_min_b;
117 C41TextBox *fix_light;
118 C41TextBox *fix_gamma_g;
119 C41TextBox *fix_gamma_b;
125 class C41Effect : public PluginVClient
128 C41Effect(PluginServer *server);
130 PLUGIN_CLASS_MEMBERS(C41Config);
132 int process_buffer(VFrame *frame,
133 int64_t start_position,
136 void save_data(KeyFrame *keyframe);
137 void read_data(KeyFrame *keyframe);
139 void render_gui(void* data);
142 float myLog2(float i) __attribute__ ((optimize(0)));
143 float myPow2(float i) __attribute__ ((optimize(0)));
144 float myPow(float a, float b);
145 double difftime_nano(timespec start, timespec end);
151 REGISTER_PLUGIN(C41Effect);
154 /* Methods decarations */
157 C41Config::C41Config()
162 fix_min_r = fix_min_g = fix_min_b = fix_light = fix_gamma_g = fix_gamma_b = 0.;
165 void C41Config::copy_from(C41Config &src)
168 compute_magic = src.compute_magic;
170 fix_min_r = src.fix_min_r;
171 fix_min_g = src.fix_min_g;
172 fix_min_b = src.fix_min_b;
173 fix_light = src.fix_light;
174 fix_gamma_g = src.fix_gamma_g;
175 fix_gamma_b = src.fix_gamma_b;
178 int C41Config::equivalent(C41Config &src)
180 return (src.active == active &&
181 compute_magic == src.compute_magic &&
182 EQUIV(src.fix_min_r, fix_min_r) &&
183 EQUIV(src.fix_min_g, fix_min_g) &&
184 EQUIV(src.fix_min_b, fix_min_b) &&
185 EQUIV(src.fix_light, fix_light) &&
186 EQUIV(src.fix_gamma_g, fix_gamma_g) &&
187 EQUIV(src.fix_gamma_b, fix_gamma_b));
190 void C41Config::interpolate(C41Config &prev,
196 active = prev.active;
197 compute_magic = prev.compute_magic;
199 fix_min_r = prev.fix_min_r;
200 fix_min_g = prev.fix_min_g;
201 fix_min_b = prev.fix_min_b;
202 fix_light = prev.fix_light;
203 fix_gamma_g = prev.fix_gamma_g;
204 fix_gamma_b = prev.fix_gamma_b;
208 C41Enable::C41Enable(C41Effect *plugin, int *output, int x, int y, char *text)
209 : BC_CheckBox(x, y, *output, text)
211 this->plugin = plugin;
212 this->output = output;
215 int C41Enable::handle_event()
217 *output = get_value();
218 plugin->send_configure_change();
223 C41TextBox::C41TextBox(C41Effect *plugin, float *value, int x, int y)
224 : BC_TextBox(x, y, 160, 1, *value)
226 this->plugin = plugin;
227 this->boxValue = value;
230 int C41TextBox::handle_event()
232 *boxValue = atof(get_text());
233 plugin->send_configure_change();
239 C41Button::C41Button(C41Effect *plugin, C41Window *window, int x, int y)
240 : BC_GenericButton(x, y, _("Lock parameters"))
242 this->plugin = plugin;
243 this->window = window;
246 int C41Button::handle_event()
248 plugin->config.fix_min_r = plugin->values.min_r;
249 plugin->config.fix_min_g = plugin->values.min_g;
250 plugin->config.fix_min_b = plugin->values.min_b;
251 plugin->config.fix_light = plugin->values.light;
252 plugin->config.fix_gamma_g = plugin->values.gamma_g;
253 plugin->config.fix_gamma_b = plugin->values.gamma_b;
256 plugin->send_configure_change();
261 C41Window::C41Window(C41Effect *plugin)
262 : PluginClientWindow(plugin, 270, 620, 1, 0, 1)
264 this->plugin = plugin;
267 void C41Window::create_objects()
272 add_subwindow(active = new C41Enable(plugin, &plugin->config.active, x, y, _("Activate processing")));
275 add_subwindow(compute_magic = new C41Enable(plugin, &plugin->config.compute_magic, x, y, _("Compute negfix values")));
277 add_subwindow(new BC_Title(x + 20, y, _("(uncheck for faster rendering)")));
280 add_subwindow(new BC_Title(x, y, _("Computed negfix values:")));
283 add_subwindow(new BC_Title(x, y, _("Min R:")));
284 add_subwindow(min_r = new BC_Title(x + 80, y, "0.0000"));
287 add_subwindow(new BC_Title(x, y, _("Min G:")));
288 add_subwindow(min_g = new BC_Title(x + 80, y, "0.0000"));
291 add_subwindow(new BC_Title(x, y, _("Min B:")));
292 add_subwindow(min_b = new BC_Title(x + 80, y, "0.0000"));
295 add_subwindow(new BC_Title(x, y, _("Light:")));
296 add_subwindow(light = new BC_Title(x + 80, y, "0.0000"));
299 add_subwindow(new BC_Title(x, y, _("Gamma G:")));
300 add_subwindow(gamma_g = new BC_Title(x + 80, y, "0.0000"));
303 add_subwindow(new BC_Title(x, y, _("Gamma B:")));
304 add_subwindow(gamma_b = new BC_Title(x + 80, y, "0.0000"));
307 add_subwindow(lock = new C41Button(plugin, this, x, y));
311 add_subwindow(new BC_Title(x, y, _("negfix values to apply:")));
314 add_subwindow(new BC_Title(x, y, _("Min R:")));
315 add_subwindow(fix_min_r = new C41TextBox(plugin, &plugin->config.fix_min_r, x + 80, y));
318 add_subwindow(new BC_Title(x, y, _("Min G:")));
319 add_subwindow(fix_min_g = new C41TextBox(plugin, &plugin->config.fix_min_g, x + 80, y));
322 add_subwindow(new BC_Title(x, y, _("Min B:")));
323 add_subwindow(fix_min_b = new C41TextBox(plugin, &plugin->config.fix_min_b, x + 80, y));
326 add_subwindow(new BC_Title(x, y, _("Light:")));
327 add_subwindow(fix_light = new C41TextBox(plugin, &plugin->config.fix_light, x + 80, y));
330 add_subwindow(new BC_Title(x, y, _("Gamma G:")));
331 add_subwindow(fix_gamma_g = new C41TextBox(plugin, &plugin->config.fix_gamma_g, x + 80, y));
334 add_subwindow(new BC_Title(x, y, _("Gamma B:")));
335 add_subwindow(fix_gamma_b = new C41TextBox(plugin, &plugin->config.fix_gamma_b, x + 80, y));
342 void C41Window::update()
344 active->update(plugin->config.active);
345 compute_magic->update(plugin->config.compute_magic);
347 fix_min_r->update(plugin->config.fix_min_r);
348 fix_min_g->update(plugin->config.fix_min_g);
349 fix_min_b->update(plugin->config.fix_min_b);
350 fix_light->update(plugin->config.fix_light);
351 fix_gamma_g->update(plugin->config.fix_gamma_g);
352 fix_gamma_b->update(plugin->config.fix_gamma_b);
357 void C41Window::update_magic()
359 min_r->update(plugin->values.min_r);
360 min_g->update(plugin->values.min_g);
361 min_b->update(plugin->values.min_b);
362 light->update(plugin->values.light);
363 gamma_g->update(plugin->values.gamma_g);
364 gamma_b->update(plugin->values.gamma_b);
369 C41Effect::C41Effect(PluginServer *server)
370 : PluginVClient(server)
372 memset(&values, 0, sizeof(values));
375 C41Effect::~C41Effect()
379 const char* C41Effect::plugin_title() { return _("C41"); }
381 int C41Effect::is_realtime() { return 1; }
383 LOAD_CONFIGURATION_MACRO(C41Effect, C41Config)
386 NEW_WINDOW_MACRO(C41Effect, C41Window)
388 void C41Effect::update_gui()
390 if(thread && load_configuration())
392 C41Window *window = (C41Window *)thread->window;
393 window->lock_window("C41Effect::update_gui");
395 window->unlock_window();
399 void C41Effect::render_gui(void* data)
401 // Updating values computed by process_frame
402 struct magic *vp = (struct magic *)data;
406 C41Window *window = (C41Window *)thread->window;
407 window->update_magic();
411 void C41Effect::save_data(KeyFrame *keyframe)
414 output.set_shared_output(keyframe->get_data(), MESSAGESIZE);
415 output.tag.set_title("C41");
416 output.tag.set_property("ACTIVE", config.active);
417 output.tag.set_property("COMPUTE_MAGIC", config.compute_magic);
419 output.tag.set_property("FIX_MIN_R", config.fix_min_r);
420 output.tag.set_property("FIX_MIN_G", config.fix_min_g);
421 output.tag.set_property("FIX_MIN_B", config.fix_min_b);
422 output.tag.set_property("FIX_LIGHT", config.fix_light);
423 output.tag.set_property("FIX_GAMMA_G", config.fix_gamma_g);
424 output.tag.set_property("FIX_GAMMA_B", config.fix_gamma_b);
427 output.tag.set_title("/C41");
429 output.terminate_string();
432 void C41Effect::read_data(KeyFrame *keyframe)
435 input.set_shared_input(keyframe->get_data(), strlen(keyframe->get_data()));
436 while(!input.read_tag())
438 if(input.tag.title_is("C41"))
440 config.active = input.tag.get_property("ACTIVE", config.active);
441 config.compute_magic = input.tag.get_property("COMPUTE_MAGIC", config.compute_magic);
443 config.fix_min_r = input.tag.get_property("FIX_MIN_R", config.fix_min_r);
444 config.fix_min_g = input.tag.get_property("FIX_MIN_G", config.fix_min_g);
445 config.fix_min_b = input.tag.get_property("FIX_MIN_B", config.fix_min_b);
446 config.fix_light = input.tag.get_property("FIX_LIGHT", config.fix_light);
447 config.fix_gamma_g = input.tag.get_property("FIX_GAMMA_G", config.fix_gamma_g);
448 config.fix_gamma_b = input.tag.get_property("FIX_GAMMA_B", config.fix_gamma_b);
454 /* Faster pow() approximation; borrowed from http://www.dctsystems.co.uk/Software/power.html
455 * Tests on real-world data showed a max error of 4% and avg. error or .1 to .5%,
456 * while accelerating rendering by a factor of 4.
458 float C41Effect::myLog2(float i)
462 float LogBodge = 0.346607f;
464 x *= 1.0 / (1 << 23); // 1/pow(2,23);
468 y = (y - y * y) * LogBodge;
472 float C41Effect::myPow2(float i)
474 float PowBodge = 0.33971f;
476 float y = i - floorf(i);
477 y = (y - y * y) * PowBodge;
485 float C41Effect::myPow(float a, float b)
487 return myPow2(b * myLog2(a));
491 int C41Effect::process_buffer(VFrame *vframe,
492 int64_t start_position,
495 load_configuration();
496 VFrame *frame = vframe;
497 read_frame(frame, 0, start_position, frame_rate, 0);
499 int frame_w = frame->get_w();
500 int frame_h = frame->get_h();
501 int color_model = frame->get_color_model();
502 int active_model = BC_CModels::has_alpha(color_model) ?
503 BC_RGBA_FLOAT : BC_RGB_FLOAT;
504 int components = active_model == BC_RGBA_FLOAT ? 4 : 3;
506 if( color_model != active_model ) {
507 new_temp(frame_w, frame_h, active_model);
509 frame->transfer_from(vframe);
512 if(config.compute_magic) {
514 VFrame* tmp_frame = new VFrame(*frame);
515 VFrame* blurry_frame = new VFrame(*frame);
517 float** rows = (float**)frame->get_rows();
518 float** tmp_rows = (float**)tmp_frame->get_rows();
519 float** blurry_rows = (float**)blurry_frame->get_rows();
520 for(int i = 0; i < frame_h; i++)
521 for(int j = 0; j < (components*frame_w); j++)
522 blurry_rows[i][j] = rows[i][j];
524 int boxw = 5, boxh = 5;
525 // 10 passes of Box blur should be good
526 int pass, x, y, y_up, y_down, x_right, x_left;
528 for(pass=0; pass<10; pass++) {
529 for(y = 0; y < frame_h; y++)
530 for(x = 0; x < (components * frame_w); x++)
531 tmp_rows[y][x] = blurry_rows[y][x];
532 for(y = 0; y < frame_h; y++) {
533 y_up = (y - boxh < 0)? 0 : y - boxh;
534 y_down = (y + boxh >= frame_h)? frame_h - 1 : y + boxh;
535 for(x = 0; x < (components*frame_w); x++) {
536 x_left = (x-(components*boxw) < 0)? 0 : x-(components*boxw);
537 x_right = (x+(components*boxw) >= (components*frame_w)) ?
538 (components*frame_w)-1 : x+(components*boxw);
539 component = (tmp_rows[y_down][x_right]
540 +tmp_rows[y_up][x_left]
541 +tmp_rows[y_up][x_right]
542 +tmp_rows[y_down][x_right])/4;
543 blurry_rows[y][x] = component;
548 // Compute magic negfix values
549 float minima_r = 50., minima_g = 50., minima_b = 50.;
550 float maxima_r = 0., maxima_g = 0., maxima_b = 0.;
552 // Shave the image in order to avoid black borders
553 // Tolerance default: 5%, i.e. 0.05
554 #define TOLERANCE 0.20
555 #define SKIP_ROW if (i < (TOLERANCE * frame_h) || i > ((1-TOLERANCE)*frame_h)) continue
556 #define SKIP_COL if (j < (TOLERANCE * frame_w) || j > ((1-TOLERANCE)*frame_w)) continue
557 for(int i = 0; i < frame_h; i++)
560 float *row = (float*)blurry_frame->get_rows()[i];
561 for(int j = 0; j < frame_w; j++, row += components) {
563 if(row[0] < minima_r) minima_r = row[0];
564 if(row[0] > maxima_r) maxima_r = row[0];
566 if(row[1] < minima_g) minima_g = row[1];
567 if(row[1] > maxima_g) maxima_g = row[1];
569 if(row[2] < minima_b) minima_b = row[2];
570 if(row[2] > maxima_b) maxima_b = row[2];
574 // Delete the VFrames we used for blurring
578 if( minima_r < 1e-3) minima_r = 1e-3;
579 if( minima_g < 1e-3) minima_g = 1e-3;
580 if( minima_b < 1e-3) minima_b = 1e-3;
581 values.min_r = minima_r;
582 values.min_g = minima_g;
583 values.min_b = minima_b;
584 values.light = (minima_r / maxima_r) * 0.95;
585 values.gamma_g = fabs(maxima_g - minima_g) < 1e-3 ? 1. :
586 logf(maxima_r / minima_r) / logf(maxima_g / minima_g);
587 values.gamma_b = fabs(maxima_b - minima_b) < 1e-3 ? 1. :
588 logf(maxima_r / minima_r) / logf(maxima_b / minima_b);
591 send_render_gui(&values);
594 // Apply the transformation
596 // Get the values from the config instead of the computed ones
597 for(int i = 0; i < frame_h; i++) {
598 float *row = (float*)frame->get_rows()[i];
599 for(int j = 0; j < frame_w; j++, row += components) {
600 row[0] = row[0] < 1e-3 ? 1.f :
601 (config.fix_min_r / row[0]) - config.fix_light;
602 row[1] = row[1] < 1e-3 ? 1.f :
603 myPow((config.fix_min_g / row[1]), config.fix_gamma_g) - config.fix_light;
604 row[2] = row[2] < 1e-3 ? 1.f :
605 myPow((config.fix_min_b / row[2]), config.fix_gamma_b) - config.fix_light;
610 if( vframe != frame )
611 vframe->transfer_from(frame);