remove whitespace at eol
[goodguy/history.git] / cinelerra-5.1 / plugins / C41 / c41.C
1 /*
2  * C41 plugin for Cinelerra
3  * Copyright (C) 2011 Florent Delannoy <florent at plui dot es>
4  *
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.
9  *
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.
14  *
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
18  *
19  */
20
21 #include "bcdisplayinfo.h"
22 #include "bchash.h"
23 #include "clip.h"
24 #include "filexml.h"
25 #include "guicast.h"
26 #include "language.h"
27 #include "cicolors.h"
28 #include "pluginvclient.h"
29 #include "vframe.h"
30
31 #include <stdint.h>
32 #include <string.h>
33
34 /* Class declarations */
35 class C41Effect;
36 class C41Window;
37
38 struct magic
39 {
40         float min_r;
41         float min_g;
42         float min_b;
43         float light;
44         float gamma_g;
45         float gamma_b;
46 };
47
48 class C41Config
49 {
50 public:
51         C41Config();
52
53         void copy_from(C41Config &src);
54         int equivalent(C41Config &src);
55         void interpolate(C41Config &prev,
56                         C41Config &next,
57                         long prev_frame,
58                         long next_frame,
59                         long current_frame);
60
61         int active;
62         int compute_magic;
63         float fix_min_r;
64         float fix_min_g;
65         float fix_min_b;
66         float fix_light;
67         float fix_gamma_g;
68         float fix_gamma_b;
69 };
70
71 class C41Enable : public BC_CheckBox
72 {
73 public:
74         C41Enable(C41Effect *plugin, int *output, int x, int y, char *text);
75         int handle_event();
76         C41Effect *plugin;
77         int *output;
78 };
79
80 class C41TextBox : public BC_TextBox
81 {
82 public:
83         C41TextBox(C41Effect *plugin, float *value, int x, int y);
84         int handle_event();
85         C41Effect *plugin;
86         float *boxValue;
87 };
88
89 class C41Button : public BC_GenericButton
90 {
91 public:
92         C41Button(C41Effect *plugin, C41Window *window, int x, int y);
93         int handle_event();
94         C41Effect *plugin;
95         C41Window *window;
96         float *boxValue;
97 };
98
99 class C41Window : public PluginClientWindow
100 {
101 public:
102         C41Window(C41Effect *plugin);
103         void create_objects();
104         void update();
105         void update_magic();
106         C41Enable *active;
107         C41Enable *compute_magic;
108         BC_Title *min_r;
109         BC_Title *min_g;
110         BC_Title *min_b;
111         BC_Title *light;
112         BC_Title *gamma_g;
113         BC_Title *gamma_b;
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;
120         C41Button *lock;
121         C41Effect *plugin;
122 };
123
124
125 class C41Effect : public PluginVClient
126 {
127 public:
128         C41Effect(PluginServer *server);
129         ~C41Effect();
130         PLUGIN_CLASS_MEMBERS(C41Config);
131
132         int process_buffer(VFrame *frame,
133                         int64_t start_position,
134                         double frame_rate);
135         int is_realtime();
136         void save_data(KeyFrame *keyframe);
137         void read_data(KeyFrame *keyframe);
138         void update_gui();
139         void render_gui(void* data);
140         int show_gui();
141         void raise_window();
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);
146
147         struct magic values;
148 };
149
150
151 REGISTER_PLUGIN(C41Effect);
152
153
154 /* Methods decarations */
155
156 // C41Config
157 C41Config::C41Config()
158 {
159         active = 0;
160         compute_magic = 0;
161
162         fix_min_r = fix_min_g = fix_min_b = fix_light = fix_gamma_g = fix_gamma_b = 0.;
163 }
164
165 void C41Config::copy_from(C41Config &src)
166 {
167         active = src.active;
168         compute_magic = src.compute_magic;
169
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;
176 }
177
178 int C41Config::equivalent(C41Config &src)
179 {
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));
188 }
189
190 void C41Config::interpolate(C41Config &prev,
191                 C41Config &next,
192                 long prev_frame,
193                 long next_frame,
194                 long current_frame)
195 {
196         active = prev.active;
197         compute_magic = prev.compute_magic;
198
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;
205 }
206
207 // C41Enable
208 C41Enable::C41Enable(C41Effect *plugin, int *output, int x, int y, char *text)
209  : BC_CheckBox(x, y, *output, text)
210 {
211         this->plugin = plugin;
212         this->output = output;
213 }
214
215 int C41Enable::handle_event()
216 {
217         *output = get_value();
218         plugin->send_configure_change();
219         return 1;
220 }
221
222 // C41TextBox
223 C41TextBox::C41TextBox(C41Effect *plugin, float *value, int x, int y)
224  : BC_TextBox(x, y, 160, 1, *value)
225 {
226         this->plugin = plugin;
227         this->boxValue = value;
228 }
229
230 int C41TextBox::handle_event()
231 {
232         *boxValue = atof(get_text());
233         plugin->send_configure_change();
234         return 1;
235 }
236
237
238 // C41Button
239 C41Button::C41Button(C41Effect *plugin, C41Window *window, int x, int y)
240  : BC_GenericButton(x, y, _("Lock parameters"))
241 {
242         this->plugin = plugin;
243         this->window = window;
244 }
245
246 int C41Button::handle_event()
247 {
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;
254
255         window->update();
256         plugin->send_configure_change();
257         return 1;
258 }
259
260 // C41Window
261 C41Window::C41Window(C41Effect *plugin)
262  : PluginClientWindow(plugin, 270, 620, 1, 0, 1)
263 {
264         this->plugin = plugin;
265 }
266
267 void C41Window::create_objects()
268 {
269         int x = 10;
270         int y = 10;
271
272         add_subwindow(active = new C41Enable(plugin, &plugin->config.active, x, y, _("Activate processing")));
273         y += 40;
274
275         add_subwindow(compute_magic = new C41Enable(plugin, &plugin->config.compute_magic, x, y, _("Compute negfix values")));
276         y += 20;
277         add_subwindow(new BC_Title(x + 20, y, _("(uncheck for faster rendering)")));
278         y += 40;
279
280         add_subwindow(new BC_Title(x, y, _("Computed negfix values:")));
281         y += 30;
282
283         add_subwindow(new BC_Title(x, y, _("Min R:")));
284         add_subwindow(min_r = new BC_Title(x + 80, y, "0.0000"));
285         y += 30;
286
287         add_subwindow(new BC_Title(x, y, _("Min G:")));
288         add_subwindow(min_g = new BC_Title(x + 80, y, "0.0000"));
289         y += 30;
290
291         add_subwindow(new BC_Title(x, y, _("Min B:")));
292         add_subwindow(min_b = new BC_Title(x + 80, y, "0.0000"));
293         y += 30;
294
295         add_subwindow(new BC_Title(x, y, _("Light:")));
296         add_subwindow(light = new BC_Title(x + 80, y, "0.0000"));
297         y += 30;
298
299         add_subwindow(new BC_Title(x, y, _("Gamma G:")));
300         add_subwindow(gamma_g = new BC_Title(x + 80, y, "0.0000"));
301         y += 30;
302
303         add_subwindow(new BC_Title(x, y, _("Gamma B:")));
304         add_subwindow(gamma_b = new BC_Title(x + 80, y, "0.0000"));
305         y += 30;
306
307         add_subwindow(lock = new C41Button(plugin, this, x, y));
308         y += 30;
309
310         y += 20;
311         add_subwindow(new BC_Title(x, y, _("negfix values to apply:")));
312         y += 30;
313
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));
316         y += 30;
317
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));
320         y += 30;
321
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));
324         y += 30;
325
326         add_subwindow(new BC_Title(x, y, _("Light:")));
327         add_subwindow(fix_light = new C41TextBox(plugin, &plugin->config.fix_light, x + 80, y));
328         y += 30;
329
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));
332         y += 30;
333
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));
336         y += 30;
337         show_window();
338         flush();
339         update_magic();
340 }
341
342 void C41Window::update()
343 {
344         active->update(plugin->config.active);
345         compute_magic->update(plugin->config.compute_magic);
346
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);
353
354         update_magic();
355 }
356
357 void C41Window::update_magic()
358 {
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);
365 }
366
367
368 // C41Effect
369 C41Effect::C41Effect(PluginServer *server)
370  : PluginVClient(server)
371 {
372         memset(&values, 0, sizeof(values));
373 }
374
375 C41Effect::~C41Effect()
376 {
377 }
378
379 const char* C41Effect::plugin_title() { return _("C41"); }
380
381 int C41Effect::is_realtime() { return 1; }
382
383 LOAD_CONFIGURATION_MACRO(C41Effect, C41Config)
384
385
386 NEW_WINDOW_MACRO(C41Effect, C41Window)
387
388 void C41Effect::update_gui()
389 {
390         if(thread && load_configuration())
391         {
392                 C41Window  *window = (C41Window *)thread->window;
393                 window->lock_window("C41Effect::update_gui");
394                 window->update();
395                 window->unlock_window();
396         }
397 }
398
399 void C41Effect::render_gui(void* data)
400 {
401         // Updating values computed by process_frame
402         struct magic *vp = (struct magic *)data;
403
404         values = *vp;
405         if(thread) {
406                 C41Window  *window = (C41Window *)thread->window;
407                 window->update_magic();
408         }
409 }
410
411 void C41Effect::save_data(KeyFrame *keyframe)
412 {
413         FileXML output;
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);
418
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);
425
426         output.append_tag();
427         output.tag.set_title("/C41");
428         output.append_tag();
429         output.terminate_string();
430 }
431
432 void C41Effect::read_data(KeyFrame *keyframe)
433 {
434         FileXML input;
435         input.set_shared_input(keyframe->get_data(), strlen(keyframe->get_data()));
436         while(!input.read_tag())
437         {
438                 if(input.tag.title_is("C41"))
439                 {
440                         config.active = input.tag.get_property("ACTIVE", config.active);
441                         config.compute_magic = input.tag.get_property("COMPUTE_MAGIC", config.compute_magic);
442
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);
449                 }
450         }
451 }
452
453
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.
457  */
458 float C41Effect::myLog2(float i)
459 {
460         float x;
461         float y;
462         float LogBodge = 0.346607f;
463         union { float f; int i; } v;
464         v.f = i;  x = v.i;
465         x *= 1.0 / (1 << 23); // 1/pow(2,23);
466         x = x - 127;
467
468         y = x - floorf(x);
469         y = (y - y * y) * LogBodge;
470         return x + y;
471 }
472
473 float C41Effect::myPow2(float i)
474 {
475         float PowBodge = 0.33971f;
476         float x;
477         float y = i - floorf(i);
478         y = (y - y * y) * PowBodge;
479
480         x = i + 127 - y;
481         x *= (1 << 23);
482         union { float f; int i; } v;
483         v.i = (int)x;  x = v.f;
484         return x;
485 }
486
487 float C41Effect::myPow(float a, float b)
488 {
489         return myPow2(b * myLog2(a));
490 }
491
492
493 int C41Effect::process_buffer(VFrame *vframe,
494                 int64_t start_position,
495                 double frame_rate)
496 {
497         load_configuration();
498         VFrame *frame = vframe;
499         read_frame(frame, 0, start_position, frame_rate, 0);
500
501         int frame_w = frame->get_w();
502         int frame_h = frame->get_h();
503         int color_model = frame->get_color_model();
504         int active_model = BC_CModels::has_alpha(color_model) ?
505                 BC_RGBA_FLOAT : BC_RGB_FLOAT;
506         int components = active_model == BC_RGBA_FLOAT ? 4 : 3;
507
508         if( color_model != active_model ) {
509                 new_temp(frame_w, frame_h, active_model);
510                 frame = get_temp();
511                 frame->transfer_from(vframe);
512         }
513
514         if(config.compute_magic) {
515                 // Box blur!
516                 VFrame* tmp_frame = new VFrame(*frame);
517                 VFrame* blurry_frame = new VFrame(*frame);
518
519                 float** rows = (float**)frame->get_rows();
520                 float** tmp_rows = (float**)tmp_frame->get_rows();
521                 float** blurry_rows = (float**)blurry_frame->get_rows();
522                 for(int i = 0; i < frame_h; i++)
523                         for(int j = 0; j < (components*frame_w); j++)
524                                 blurry_rows[i][j] = rows[i][j];
525
526                 int boxw = 5, boxh = 5;
527                 // 10 passes of Box blur should be good
528                 int pass, x, y, y_up, y_down, x_right, x_left;
529                 float component;
530                 for(pass=0; pass<10; pass++) {
531                         for(y = 0; y < frame_h; y++)
532                                 for(x = 0; x < (components * frame_w); x++)
533                                         tmp_rows[y][x] = blurry_rows[y][x];
534                         for(y = 0; y < frame_h; y++) {
535                                 y_up = (y - boxh < 0)? 0 : y - boxh;
536                                 y_down = (y + boxh >= frame_h)? frame_h - 1 : y + boxh;
537                                 for(x = 0; x < (components*frame_w); x++) {
538                                         x_left = (x-(components*boxw) < 0)? 0 : x-(components*boxw);
539                                         x_right = (x+(components*boxw) >= (components*frame_w)) ?
540                                                 (components*frame_w)-1 : x+(components*boxw);
541                                         component = (tmp_rows[y_down][x_right]
542                                                         +tmp_rows[y_up][x_left]
543                                                         +tmp_rows[y_up][x_right]
544                                                         +tmp_rows[y_down][x_right])/4;
545                                         blurry_rows[y][x] = component;
546                                 }
547                         }
548                 }
549
550                 // Compute magic negfix values
551                 float minima_r = 50., minima_g = 50., minima_b = 50.;
552                 float maxima_r = 0.,  maxima_g = 0.,  maxima_b = 0.;
553
554                 // Shave the image in order to avoid black borders
555                 // Tolerance default: 5%, i.e. 0.05
556 #define TOLERANCE 0.20
557 #define SKIP_ROW if (i < (TOLERANCE * frame_h) || i > ((1-TOLERANCE)*frame_h)) continue
558 #define SKIP_COL if (j < (TOLERANCE * frame_w) || j > ((1-TOLERANCE)*frame_w)) continue
559 for(int i = 0; i < frame_h; i++)
560                 {
561                         SKIP_ROW;
562                         float *row = (float*)blurry_frame->get_rows()[i];
563                         for(int j = 0; j < frame_w; j++, row += components) {
564                                 SKIP_COL;
565                                 if(row[0] < minima_r) minima_r = row[0];
566                                 if(row[0] > maxima_r) maxima_r = row[0];
567
568                                 if(row[1] < minima_g) minima_g = row[1];
569                                 if(row[1] > maxima_g) maxima_g = row[1];
570
571                                 if(row[2] < minima_b) minima_b = row[2];
572                                 if(row[2] > maxima_b) maxima_b = row[2];
573                         }
574                 }
575
576                 // Delete the VFrames we used for blurring
577                 delete tmp_frame;
578                 delete blurry_frame;
579
580                 if( minima_r < 1e-3) minima_r = 1e-3;
581                 if( minima_g < 1e-3) minima_g = 1e-3;
582                 if( minima_b < 1e-3) minima_b = 1e-3;
583                 values.min_r = minima_r;
584                 values.min_g = minima_g;
585                 values.min_b = minima_b;
586                 values.light = (minima_r / maxima_r) * 0.95;
587                 values.gamma_g = fabs(maxima_g - minima_g) < 1e-3 ? 1. :
588                         logf(maxima_r / minima_r) / logf(maxima_g / minima_g);
589                 values.gamma_b = fabs(maxima_b - minima_b) < 1e-3 ? 1. :
590                         logf(maxima_r / minima_r) / logf(maxima_b / minima_b);
591
592                 // Update GUI
593                 send_render_gui(&values);
594         }
595
596         // Apply the transformation
597         if(config.active) {
598                 // Get the values from the config instead of the computed ones
599                 for(int i = 0; i < frame_h; i++) {
600                         float *row = (float*)frame->get_rows()[i];
601                         for(int j = 0; j < frame_w; j++, row += components) {
602                                 row[0] = row[0] < 1e-3 ? 1.f :
603                                         (config.fix_min_r / row[0]) - config.fix_light;
604                                 row[1] = row[1] < 1e-3 ? 1.f :
605                                         myPow((config.fix_min_g / row[1]), config.fix_gamma_g) - config.fix_light;
606                                 row[2] = row[2] < 1e-3 ? 1.f :
607                                         myPow((config.fix_min_b / row[2]), config.fix_gamma_b) - config.fix_light;
608                         }
609                 }
610         }
611
612         if( vframe != frame )
613                 vframe->transfer_from(frame);
614
615         return 0;
616 }
617