plug leaks, leaker tweaks, lang for effect info, c41 spiffs, wm probe tweaks
[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 #include "c41.h"
21 #include "cicolors.h"
22 #include "clip.h"
23 #include "edlsession.h"
24 #include "filexml.h"
25 #include "language.h"
26 #include "vframe.h"
27
28 REGISTER_PLUGIN(C41Effect);
29
30 C41Config::C41Config()
31 {
32         active = 0;
33         compute_magic = 0;
34         postproc = 0;
35         show_box = 0;
36
37         fix_min_r = fix_min_g = fix_min_b = fix_light = 0.;
38         fix_gamma_g = fix_gamma_b = fix_coef1 = fix_coef2 = 0.;
39         min_col = max_col = min_row = max_row = 0;
40         window_w = 500; window_h = 510;
41 }
42
43 void C41Config::copy_from(C41Config &src)
44 {
45         active = src.active;
46         compute_magic = src.compute_magic;
47         postproc = src.postproc;
48         show_box = src.show_box;
49
50         fix_min_r = src.fix_min_r;
51         fix_min_g = src.fix_min_g;
52         fix_min_b = src.fix_min_b;
53         fix_light = src.fix_light;
54         fix_gamma_g = src.fix_gamma_g;
55         fix_gamma_b = src.fix_gamma_b;
56         fix_coef1 = src.fix_coef1;
57         fix_coef2 = src.fix_coef2;
58         min_row = src.min_row;
59         max_row = src.max_row;
60         min_col = src.min_col;
61         max_col = src.max_col;
62 }
63
64 int C41Config::equivalent(C41Config &src)
65 {
66         return (active == src.active &&
67                 compute_magic == src.compute_magic &&
68                 postproc == src.postproc &&
69                 show_box == src.show_box &&
70                 EQUIV(fix_min_r, src.fix_min_r) &&
71                 EQUIV(fix_min_g, src.fix_min_g) &&
72                 EQUIV(fix_min_b, src.fix_min_b) &&
73                 EQUIV(fix_light, src.fix_light) &&
74                 EQUIV(fix_gamma_g, src.fix_gamma_g) &&
75                 EQUIV(fix_gamma_b, src.fix_gamma_b) &&
76                 EQUIV(fix_coef1, src.fix_coef1) &&
77                 EQUIV(fix_coef2, src.fix_coef2) &&
78                 min_row == src.min_row &&
79                 max_row == src.max_row &&
80                 min_col == src.min_col &&
81                 max_col == src.max_col);
82 }
83
84 void C41Config::interpolate(C41Config &prev, C41Config &next,
85                 long prev_frame, long next_frame, long current_frame)
86 {
87         active = prev.active;
88         compute_magic = prev.compute_magic;
89         postproc = prev.postproc;
90         show_box = prev.show_box;
91
92         double next_scale = (double)(current_frame - prev_frame) / (next_frame - prev_frame);
93         double prev_scale = (double)(next_frame - current_frame) / (next_frame - prev_frame);
94         fix_min_r = prev.fix_min_r * prev_scale + next.fix_min_r * next_scale;
95         fix_min_g = prev.fix_min_g * prev_scale + next.fix_min_g * next_scale;
96         fix_min_b = prev.fix_min_b * prev_scale + next.fix_min_b * next_scale;
97         fix_light = prev.fix_light * prev_scale + next.fix_light * next_scale;
98         fix_gamma_g = prev.fix_gamma_g * prev_scale + next.fix_gamma_g * next_scale;
99         fix_gamma_b = prev.fix_gamma_b * prev_scale + next.fix_gamma_b * next_scale;
100         fix_coef1 = prev.fix_coef1 * prev_scale + next.fix_coef1 * next_scale;
101         fix_coef2 = prev.fix_coef2 * prev_scale + next.fix_coef2 * next_scale;
102         min_row = round(prev.min_row * prev_scale + next.min_row * next_scale);
103         min_col = round(prev.min_col * prev_scale + next.min_col * next_scale);
104         max_row = round(prev.max_row * prev_scale + next.max_row * next_scale);
105         max_col = round(prev.max_col * prev_scale + next.max_col * next_scale);
106 }
107
108 // C41Enable
109 C41Enable::C41Enable(C41Effect *plugin, int *output, int x, int y, const char *text)
110  : BC_CheckBox(x, y, *output, text)
111 {
112         this->plugin = plugin;
113         this->output = output;
114 }
115
116 int C41Enable::handle_event()
117 {
118         *output = get_value();
119         plugin->send_configure_change();
120         return 1;
121 }
122
123 // C41TextBox
124 C41TextBox::C41TextBox(C41Effect *plugin, float *value, int x, int y)
125  : BC_TextBox(x, y, 160, 1, *value)
126 {
127         this->plugin = plugin;
128         this->boxValue = value;
129 }
130
131 int C41TextBox::handle_event()
132 {
133         *boxValue = atof(get_text());
134         plugin->send_configure_change();
135         return 1;
136 }
137
138
139 // C41Button
140 C41Button::C41Button(C41Effect *plugin, C41Window *window, int x, int y)
141  : BC_GenericButton(x, y, _("Apply values"))
142 {
143         this->plugin = plugin;
144         this->window = window;
145 }
146
147 int C41Button::handle_event()
148 {
149         plugin->config.fix_min_r = plugin->values.min_r;
150         plugin->config.fix_min_g = plugin->values.min_g;
151         plugin->config.fix_min_b = plugin->values.min_b;
152         plugin->config.fix_light = plugin->values.light;
153         plugin->config.fix_gamma_g = plugin->values.gamma_g;
154         plugin->config.fix_gamma_b = plugin->values.gamma_b;
155         if( plugin->values.coef1 > 0 )
156                 plugin->config.fix_coef1 = plugin->values.coef1;
157         if( plugin->values.coef2 > 0 )
158                 plugin->config.fix_coef2 = plugin->values.coef2;
159
160         window->update();
161
162         plugin->send_configure_change();
163         return 1;
164 }
165
166
167 C41BoxButton::C41BoxButton(C41Effect *plugin, C41Window *window, int x, int y)
168  : BC_GenericButton(x, y, _("Apply default box"))
169 {
170         this->plugin = plugin;
171         this->window = window;
172 }
173
174 int C41BoxButton::handle_event()
175 {
176         plugin->config.min_row = plugin->values.shave_min_row;
177         plugin->config.max_row = plugin->values.shave_max_row;
178         plugin->config.min_col = plugin->values.shave_min_col;
179         plugin->config.max_col = plugin->values.shave_max_col;
180
181         window->update();
182
183         plugin->send_configure_change();
184         return 1;
185 }
186
187
188 C41Slider::C41Slider(C41Effect *plugin, int *output, int x, int y, int is_row)
189  : BC_ISlider(x, y, 0, 200, 200, 0, is_row ?
190         plugin->get_edlsession()->output_h :
191         plugin->get_edlsession()->output_w , *output)
192 {
193         this->plugin = plugin;
194         this->output = output;
195         this->is_row = is_row;
196         EDLSession *session = plugin->get_edlsession();
197         this->max = is_row ? session->output_h : session->output_w;
198 }
199
200 int C41Slider::handle_event()
201 {
202         *output = get_value();
203         plugin->send_configure_change();
204         return 1;
205 }
206
207 int C41Slider::update(int v)
208 {
209         EDLSession *session = plugin->get_edlsession();
210         int max = is_row ? session->output_h : session->output_w;
211         bclamp(v, 0, max);
212         if( this->max != max ) return BC_ISlider::update(200, v, 0, this->max = max);
213         if( v != get_value() ) return BC_ISlider::update(v);
214         return 1;
215 }
216
217 const char* C41Effect::plugin_title() { return _("C41"); }
218 int C41Effect::is_realtime() { return 1; }
219 int C41Effect::is_synthesis() { return 1; }
220
221 NEW_WINDOW_MACRO(C41Effect, C41Window);
222 LOAD_CONFIGURATION_MACRO(C41Effect, C41Config)
223
224 // C41Window
225 C41Window::C41Window(C41Effect *plugin)
226  : PluginClientWindow(plugin,
227         plugin->config.window_w, plugin->config.window_h,
228         500, 510, 1)
229 {
230         int x = 10, y = 10;
231
232         add_subwindow(active = new C41Enable(plugin,
233                 &plugin->config.active, x, y, _("Activate processing")));
234         y += 40;
235
236         add_subwindow(compute_magic = new C41Enable(plugin,
237                 &plugin->config.compute_magic, x, y, _("Compute negfix values")));
238         y += 20;
239
240         add_subwindow(new BC_Title(x + 20, y, _("(uncheck for faster rendering)")));
241         y += 40;
242
243         add_subwindow(new BC_Title(x, y, _("Computed negfix values:")));
244         y += 30;
245
246         add_subwindow(new BC_Title(x, y, _("Min/Max R:")));
247         add_subwindow(min_r = new BC_Title(x + 80, y, "0.0000 / 0.0000"));
248         y += 30;
249
250         add_subwindow(new BC_Title(x, y, _("Min/Max G:")));
251         add_subwindow(min_g = new BC_Title(x + 80, y, "0.0000 / 0.0000"));
252         y += 30;
253
254         add_subwindow(new BC_Title(x, y, _("Min/Max B:")));
255         add_subwindow(min_b = new BC_Title(x + 80, y, "0.0000 / 0.0000"));
256         y += 30;
257
258         add_subwindow(new BC_Title(x, y, _("Light:")));
259         add_subwindow(light = new BC_Title(x + 80, y, "0.0000"));
260         y += 30;
261
262         add_subwindow(new BC_Title(x, y, _("Gamma G:")));
263         add_subwindow(gamma_g = new BC_Title(x + 80, y, "0.0000"));
264         y += 30;
265
266         add_subwindow(new BC_Title(x, y, _("Gamma B:")));
267         add_subwindow(gamma_b = new BC_Title(x + 80, y, "0.0000"));
268         y += 30;
269
270         add_subwindow(new BC_Title(x, y, _("Contrast:")));
271         add_subwindow(coef1 = new BC_Title(x + 80, y, "0.0000"));
272         y += 30;
273
274         add_subwindow(new BC_Title(x, y, _("Brightness:")));
275         add_subwindow(coef2 = new BC_Title(x + 80, y, "0.0000"));
276         y += 30;
277
278         add_subwindow(lock = new C41Button(plugin, this, x, y));
279         y += 30;
280
281 #define BOX_COL 120
282         add_subwindow(new BC_Title(x, y, _("Box col:")));
283         add_subwindow(box_col_min = new BC_Title(x + 80, y, "0"));
284         add_subwindow(box_col_max = new BC_Title(x + BOX_COL, y, "0"));
285         y += 30;
286
287         add_subwindow(new BC_Title(x, y, _("Box row:")));
288         add_subwindow(box_row_min = new BC_Title(x + 80, y, "0"));
289         add_subwindow(box_row_max = new BC_Title(x + BOX_COL, y, "0"));
290         y += 30;
291
292         add_subwindow(boxlock = new C41BoxButton(plugin, this, x, y));
293
294         y = 10;
295         x = 250;
296         add_subwindow(show_box = new C41Enable(plugin,
297                 &plugin->config.show_box, x, y, _("Show active area")));
298         y += 40;
299
300         add_subwindow(postproc = new C41Enable(plugin,
301                 &plugin->config.postproc, x, y, _("Postprocess")));
302         y += 60;
303
304         add_subwindow(new BC_Title(x, y, _("negfix values to apply:")));
305         y += 30;
306
307         add_subwindow(new BC_Title(x, y, _("Min R:")));
308         add_subwindow(fix_min_r = new C41TextBox(plugin,
309                 &plugin->config.fix_min_r, x + 80, y));
310         y += 30;
311
312         add_subwindow(new BC_Title(x, y, _("Min G:")));
313         add_subwindow(fix_min_g = new C41TextBox(plugin,
314                 &plugin->config.fix_min_g, x + 80, y));
315         y += 30;
316
317         add_subwindow(new BC_Title(x, y, _("Min B:")));
318         add_subwindow(fix_min_b = new C41TextBox(plugin,
319                 &plugin->config.fix_min_b, x + 80, y));
320         y += 30;
321
322         add_subwindow(new BC_Title(x, y, _("Light:")));
323         add_subwindow(fix_light = new C41TextBox(plugin,
324                  &plugin->config.fix_light, x + 80, y));
325         y += 30;
326
327         add_subwindow(new BC_Title(x, y, _("Gamma G:")));
328         add_subwindow(fix_gamma_g = new C41TextBox(plugin,
329                  &plugin->config.fix_gamma_g, x + 80, y));
330         y += 30;
331
332         add_subwindow(new BC_Title(x, y, _("Gamma B:")));
333         add_subwindow(fix_gamma_b = new C41TextBox(plugin,
334                  &plugin->config.fix_gamma_b, x + 80, y));
335         y += 30;
336
337         add_subwindow(new BC_Title(x, y, _("Contrast:")));
338         add_subwindow(fix_coef1 = new C41TextBox(plugin,
339                  &plugin->config.fix_coef1, x + 80, y));
340         y += 30;
341
342         add_subwindow(new BC_Title(x, y, _("Brightness:")));
343         add_subwindow(fix_coef2 = new C41TextBox(plugin,
344                  &plugin->config.fix_coef2, x + 80, y));
345         y += 30;
346
347         x += 40;
348         add_subwindow(new BC_Title(x - 40, y, _("Col:")));
349         add_subwindow(min_col = new C41Slider(plugin,
350                  &plugin->config.min_col, x, y, 0));
351         y += 25;
352
353         add_subwindow(max_col = new C41Slider(plugin,
354                  &plugin->config.max_col, x, y, 0));
355         y += 25;
356
357         add_subwindow(new BC_Title(x - 40, y, _("Row:")));
358         add_subwindow(min_row = new C41Slider(plugin,
359                  &plugin->config.min_row, x, y, 1));
360         y += 25;
361
362         add_subwindow(max_row = new C41Slider(plugin,
363                  &plugin->config.max_row, x, y, 1));
364         y += 25;
365
366         update_magic();
367         show_window(1);
368 }
369
370 void C41Window::update()
371 {
372         C41Effect *plugin = (C41Effect *)client;
373         // Updating the GUI itself
374         active->update(plugin->config.active);
375         compute_magic->update(plugin->config.compute_magic);
376         postproc->update(plugin->config.postproc);
377         show_box->update(plugin->config.show_box);
378
379         fix_min_r->update(plugin->config.fix_min_r);
380         fix_min_g->update(plugin->config.fix_min_g);
381         fix_min_b->update(plugin->config.fix_min_b);
382         fix_light->update(plugin->config.fix_light);
383         fix_gamma_g->update(plugin->config.fix_gamma_g);
384         fix_gamma_b->update(plugin->config.fix_gamma_b);
385         fix_coef1->update(plugin->config.fix_coef1);
386         fix_coef2->update(plugin->config.fix_coef2);
387
388         min_row->update(plugin->config.min_row);
389         max_row->update(plugin->config.max_row);
390         min_col->update(plugin->config.min_col);
391         max_col->update(plugin->config.max_col);
392
393         update_magic();
394 }
395
396 void C41Window::update_magic()
397 {
398         C41Effect *plugin = (C41Effect *)client;
399         char text[BCSTRLEN];
400         sprintf(text, "%0.4f / %0.4f", plugin->values.min_r, plugin->values.max_r);
401         min_r->update(text);
402         sprintf(text, "%0.4f / %0.4f", plugin->values.min_g, plugin->values.max_g);
403         min_g->update(text);
404         sprintf(text, "%0.4f / %0.4f", plugin->values.min_b, plugin->values.max_b);
405         min_b->update(text);
406         light->update(plugin->values.light);
407         gamma_g->update(plugin->values.gamma_g);
408         gamma_b->update(plugin->values.gamma_b);
409         // Avoid blinking
410         if( plugin->values.coef1 > 0 || plugin->values.coef2 > 0 ) {
411                 coef1->update(plugin->values.coef1);
412                 coef2->update(plugin->values.coef2);
413         }
414         sprintf(text, "%d", plugin->values.shave_min_col);
415         box_col_min->update(text);
416         sprintf(text, "%d", plugin->values.shave_max_col);
417         box_col_max->update(text);
418         sprintf(text, "%d", plugin->values.shave_min_row);
419         box_row_min->update(text);
420         sprintf(text, "%d", plugin->values.shave_max_row);
421         box_row_max->update(text);
422 }
423
424
425 // C41Effect
426 C41Effect::C41Effect(PluginServer *server)
427 : PluginVClient(server)
428 {
429         frame = 0;
430         tmp_frame = 0;
431         blurry_frame = 0;
432         memset(&values, 0, sizeof(values));
433 }
434
435 C41Effect::~C41Effect()
436 {
437         delete frame;
438         delete tmp_frame;
439         delete blurry_frame;
440 }
441
442 void C41Effect::render_gui(void* data)
443 {
444         // Updating values computed by process_frame
445         struct magic *vp = (struct magic *)data;
446         values = *vp;
447
448         if( thread ) {
449                 C41Window *window = (C41Window *) thread->window;
450                 window->lock_window("C41Effect::render_gui");
451                 window->update_magic();
452                 window->unlock_window();
453         }
454 }
455
456 void C41Effect::save_data(KeyFrame *keyframe)
457 {
458         FileXML output;
459         output.set_shared_output(keyframe->get_data(), MESSAGESIZE);
460
461         output.tag.set_title("C41");
462         output.tag.set_property("ACTIVE", config.active);
463         output.tag.set_property("COMPUTE_MAGIC", config.compute_magic);
464         output.tag.set_property("POSTPROC", config.postproc);
465         output.tag.set_property("SHOW_BOX", config.show_box);
466
467         output.tag.set_property("FIX_MIN_R", config.fix_min_r);
468         output.tag.set_property("FIX_MIN_G", config.fix_min_g);
469         output.tag.set_property("FIX_MIN_B", config.fix_min_b);
470         output.tag.set_property("FIX_LIGHT", config.fix_light);
471         output.tag.set_property("FIX_GAMMA_G", config.fix_gamma_g);
472         output.tag.set_property("FIX_GAMMA_B", config.fix_gamma_b);
473         output.tag.set_property("FIX_COEF1", config.fix_coef1);
474         output.tag.set_property("FIX_COEF2", config.fix_coef2);
475         output.tag.set_property("MIN_ROW", config.min_row);
476         output.tag.set_property("MAX_ROW", config.max_row);
477         output.tag.set_property("MIN_COL", config.min_col);
478         output.tag.set_property("MAX_COL", config.max_col);
479         output.append_tag();
480         output.tag.set_title("/C41");
481         output.append_tag();
482         output.terminate_string();
483 }
484
485 void C41Effect::read_data(KeyFrame *keyframe)
486 {
487         FileXML input;
488         input.set_shared_input(keyframe->get_data(), strlen(keyframe->get_data()));
489
490         while(!input.read_tag())
491         {
492                 if( input.tag.title_is("C41") ) {
493                         config.active = input.tag.get_property("ACTIVE", config.active);
494                         config.compute_magic = input.tag.get_property("COMPUTE_MAGIC", config.compute_magic);
495                         config.postproc = input.tag.get_property("POSTPROC", config.postproc);
496                         config.show_box = input.tag.get_property("SHOW_BOX", config.show_box);
497                         config.fix_min_r = input.tag.get_property("FIX_MIN_R", config.fix_min_r);
498                         config.fix_min_g = input.tag.get_property("FIX_MIN_G", config.fix_min_g);
499                         config.fix_min_b = input.tag.get_property("FIX_MIN_B", config.fix_min_b);
500                         config.fix_light = input.tag.get_property("FIX_LIGHT", config.fix_light);
501                         config.fix_gamma_g = input.tag.get_property("FIX_GAMMA_G", config.fix_gamma_g);
502                         config.fix_gamma_b = input.tag.get_property("FIX_GAMMA_B", config.fix_gamma_b);
503                         config.fix_coef1 = input.tag.get_property("FIX_COEF1", config.fix_coef2);
504                         config.fix_coef2 = input.tag.get_property("FIX_COEF2", config.fix_coef2);
505                         config.min_row = input.tag.get_property("MIN_ROW", config.min_row);
506                         config.max_row = input.tag.get_property("MAX_ROW", config.max_row);
507                         config.min_col = input.tag.get_property("MIN_COL", config.min_col);
508                         config.max_col = input.tag.get_property("MAX_COL", config.max_col);
509                 }
510         }
511 }
512
513 #if defined(C41_FAST_POW)
514
515 float C41Effect::myLog2(float i)
516 {
517         float x, y;
518         float LogBodge = 0.346607f;
519         x = *(int *)&i;
520         x *= 1.0 / (1 << 23); // 1/pow(2,23);
521         x = x - 127;
522
523         y = x - floorf(x);
524         y = (y - y * y) * LogBodge;
525         return x + y;
526 }
527
528 float C41Effect::myPow2(float i)
529 {
530         float PowBodge = 0.33971f;
531         float x;
532         float y = i - floorf(i);
533         y = (y - y * y) * PowBodge;
534
535         x = i + 127 - y;
536         x *= (1 << 23);
537         *(int*)&x = (int)x;
538         return x;
539 }
540
541 float C41Effect::myPow(float a, float b)
542 {
543         return myPow2(b * myLog2(a));
544 }
545
546 #endif
547
548 void C41Effect::pix_fix(float &pix, float min, float gamma)
549 {
550         float val = pix;
551         if( val >= 1e-3 ) {
552                 val = min / val;
553                 if( gamma != 1 ) val = POWF(val, gamma);
554                 bclamp(val -= config.fix_light, 0, pix_max);
555                 if( config.postproc )
556                         val = config.fix_coef1 * val + config.fix_coef2;
557         }
558         else
559                 val = 1;
560         pix = val;
561 }
562
563 int C41Effect::process_realtime(VFrame *input, VFrame *output)
564 {
565         load_configuration();
566
567         int frame_w = input->get_w(), frame_h = input->get_h();
568         int input_model = input->get_color_model();
569         int has_alpha = BC_CModels::has_alpha(input_model);
570         int color_model = has_alpha ? BC_RGBA_FLOAT : BC_RGB_FLOAT;
571
572         if( input_model != color_model ) {
573                 if( frame &&
574                     ( frame->get_w() != frame_w || frame->get_h() != frame_h ||
575                       frame->get_color_model() != color_model ) ) {
576                         delete frame;  frame = 0;
577                 }
578                 if( !frame )
579                         frame = new VFrame(frame_w, frame_h, color_model);
580                 frame->transfer_from(input);
581         }
582         else {
583                 delete frame;
584                 frame = input;
585         }
586
587         pix_max = BC_CModels::calculate_max(color_model);
588         pix_len = BC_CModels::components(color_model);
589
590         min_row = config.min_row;  bclamp(min_row, 0,frame_h);
591         max_row = config.max_row;  bclamp(max_row, 0,frame_h);
592         min_col = config.min_col;  bclamp(min_col, 0,frame_w);
593         max_col = config.max_col;  bclamp(max_col, 0,frame_w);
594
595         if( config.compute_magic ) {
596                 // Convert frame to RGB for magic computing
597                 if( tmp_frame &&
598                     (tmp_frame->get_w() != frame_w || tmp_frame->get_h() != frame_h) ) {
599                         delete tmp_frame;  tmp_frame = 0;
600                 }
601                 if( !tmp_frame )
602                         tmp_frame = new VFrame(frame_w, frame_h, BC_RGB_FLOAT);
603                 if( blurry_frame &&
604                     (blurry_frame->get_w() != frame_w || blurry_frame->get_h() != frame_h) ) {
605                         delete blurry_frame;  blurry_frame = 0;
606                 }
607                 if( !blurry_frame )
608                         blurry_frame = new VFrame(frame_w, frame_h, BC_RGB_FLOAT);
609
610                 float **rows = (float **)frame->get_rows();
611                 float **tmp_rows = (float **)tmp_frame->get_rows();
612                 float **blurry_rows = (float **)blurry_frame->get_rows();
613
614                 for( int i=0; i<frame_h; ++i ) {
615                         float *row = rows[i], *tmp = tmp_rows[i];
616                         for( int j=frame_w; --j>=0; row+=pix_len ) {
617                                 *tmp++ = bclip(row[0], 0, pix_max);
618                                 *tmp++ = bclip(row[1], 0, pix_max);
619                                 *tmp++ = bclip(row[2], 0, pix_max);
620                         }
621                 }
622
623                 // 10 passes of Box blur should be good
624                 int dx = 5, dy = 5, y_up, y_dn, x_rt, x_lt;
625                 float **src = tmp_rows, **tgt = blurry_rows;
626                 for( int pass = 0; pass < 10; pass++ ) {
627                         for( int y = 0; y < frame_h; y++ ) {
628                                 if( (y_up=y-dy) < 0 ) y_up = 0;
629                                 if( (y_dn=y+dy) >= frame_h ) y_dn = frame_h-1;
630                                 float *up = src[y_up], *dn = src[y_dn], *row = tgt[y];
631                                 for( int x = 0; x < frame_w; ++x ) {
632                                         if( (x_lt=x-dx) < 0 ) x_lt = 0;
633                                         if( (x_rt=x+dx) >= frame_w ) x_lt = frame_w-1;
634                                         float *dn_l = dn + 3*x_lt, *dn_r = dn + 3*x_rt;
635                                         float *up_l = up + 3*x_lt, *up_r = up + 3*x_rt;
636                                         row[0] = (dn_l[0] + dn_r[0] + up_l[0] + up_r[0]) / 4;
637                                         row[1] = (dn_l[1] + dn_r[1] + up_l[1] + up_r[1]) / 4;
638                                         row[2] = (dn_l[2] + dn_r[2] + up_l[2] + up_r[2]) / 4;
639                                         row += 3;
640                                 }
641                         }
642                         float **rows = src;  src = tgt;  tgt = rows;
643                 }
644
645                 // Shave image: cut off border areas where min max difference
646                 // is less than C41_SHAVE_TOLERANCE, starting/ending at C41_SHAVE_MARGIN
647
648                 shave_min_row = shave_min_col = 0;
649                 shave_max_col = frame_w;
650                 shave_max_row = frame_h;
651                 int min_w = frame_w * C41_SHAVE_MARGIN;
652                 int max_w = frame_w * (1-C41_SHAVE_MARGIN);
653                 int min_h = frame_h * C41_SHAVE_MARGIN;
654                 int max_h = frame_h * (1-C41_SHAVE_MARGIN);
655
656                 float yv_min[frame_h], yv_max[frame_h];
657                 for( int y=0; y<frame_h; ++y ) {
658                         yv_min[y] = pix_max;
659                         yv_max[y] = 0.;
660                 }
661
662                 for( int y = 0; y < frame_h; y++ ) {
663                         float *row = blurry_rows[y];
664                         for( int x = 0; x < frame_w; x++, row += 3 ) {
665                                 float yv = (row[0] + row[1] + row[2]) / 3;
666                                 if( yv_min[y] > yv ) yv_min[y] = yv;
667                                 if( yv_max[y] < yv ) yv_max[y] = yv;
668                         }
669                 }
670
671                 for( shave_min_row = min_h; shave_min_row < max_h; shave_min_row++ )
672                         if( yv_max[shave_min_row] - yv_min[shave_min_row] > C41_SHAVE_TOLERANCE )
673                                 break;
674                 for( shave_max_row = max_h - 1; shave_max_row > shave_min_row; shave_max_row-- )
675                         if( yv_max[shave_max_row] - yv_min[shave_max_row] > C41_SHAVE_TOLERANCE )
676                                 break;
677                 shave_max_row++;
678
679                 float xv_min[frame_w], xv_max[frame_w];
680                 for( int x=0; x<frame_w; ++x ) {
681                         xv_min[x] = pix_max;
682                         xv_max[x] = 0.;
683                 }
684
685                 for( int y = shave_min_row; y < shave_max_row; y++ ) {
686                         float *row = blurry_rows[y];
687                         for( int x = 0; x < frame_w; x++, row += 3 ) {
688                                 float xv = (row[0] + row[1] + row[2]) / 3;
689                                 if( xv_min[x] > xv ) xv_min[x] = xv;
690                                 if( xv_max[x] < xv ) xv_max[x] = xv;
691                         }
692                 }
693
694                 for( shave_min_col = min_w; shave_min_col < max_w; shave_min_col++ )
695                         if( xv_max[shave_min_col] - xv_min[shave_min_col] > C41_SHAVE_TOLERANCE )
696                                 break;
697                 for( shave_max_col = max_w - 1; shave_max_col > shave_min_col; shave_max_col-- )
698                         if( xv_max[shave_max_col] - xv_min[shave_max_col] > C41_SHAVE_TOLERANCE )
699                                 break;
700                 shave_max_col++;
701
702                 // Compute magic negfix values
703                 float minima_r = 50., minima_g = 50., minima_b = 50.;
704                 float maxima_r = 0., maxima_g = 0., maxima_b = 0.;
705
706                 // Check if config_parameters are usable
707                 if( config.min_col >= config.max_col || config.min_row >= config.max_row ) {
708                         min_col = shave_min_col;
709                         max_col = shave_max_col;
710                         min_row = shave_min_row;
711                         max_row = shave_max_row;
712                 }
713
714                 for( int i = min_row; i < max_row; i++ ) {
715                         float *row = blurry_rows[i] + 3 * min_col;
716                         for( int j = min_col; j < max_col; j++, row+=3 ) {
717                                 if( row[0] < minima_r ) minima_r = row[0];
718                                 if( row[0] > maxima_r ) maxima_r = row[0];
719                                 if( row[1] < minima_g ) minima_g = row[1];
720                                 if( row[1] > maxima_g ) maxima_g = row[1];
721                                 if( row[2] < minima_b ) minima_b = row[2];
722                                 if( row[2] > maxima_b ) maxima_b = row[2];
723                         }
724                 }
725                 values.min_r = minima_r;  values.max_r = maxima_r;
726                 values.min_g = minima_g;  values.max_g = maxima_g;
727                 values.min_b = minima_b;  values.max_b = maxima_b;
728                 values.light = maxima_r < 1e-6 ? 1.0 : minima_r / maxima_r;
729                 bclamp(minima_r, 1e-6, 1-1e-6);  bclamp(maxima_r, 1e-6, 1-1e-6);
730                 bclamp(minima_g, 1e-6, 1-1e-6);  bclamp(maxima_g, 1e-6, 1-1e-6);
731                 bclamp(minima_b, 1e-6, 1-1e-6);  bclamp(maxima_b, 1e-6, 1-1e-6);
732                 float log_r = logf(maxima_r / minima_r);
733                 float log_g = logf(maxima_g / minima_g);
734                 float log_b = logf(maxima_b / minima_b);
735                 if( log_g < 1e-6 ) log_g = 1e-6;
736                 if( log_b < 1e-6 ) log_b = 1e-6;
737                 values.gamma_g = log_r / log_g;
738                 values.gamma_b = log_r / log_b;
739                 values.shave_min_col = shave_min_col;
740                 values.shave_max_col = shave_max_col;
741                 values.shave_min_row = shave_min_row;
742                 values.shave_max_row = shave_max_row;
743                 values.coef1 = values.coef2 = -1;
744
745                 // Update GUI
746                 send_render_gui(&values);
747         }
748
749         if( config.compute_magic ) {
750                 float minima_r = 50., minima_g = 50., minima_b = 50.;
751                 float maxima_r = 0., maxima_g = 0., maxima_b = 0.;
752
753                 for( int i = min_row; i < max_row; i++ ) {
754                         float *row = (float*)frame->get_rows()[i];
755                         row += 3 * shave_min_col;
756                         for( int j = min_col; j < max_col; j++, row += 3 ) {
757                                 if( row[0] < minima_r ) minima_r = row[0];
758                                 if( row[0] > maxima_r ) maxima_r = row[0];
759
760                                 if( row[1] < minima_g ) minima_g = row[1];
761                                 if( row[1] > maxima_g ) maxima_g = row[1];
762
763                                 if( row[2] < minima_b ) minima_b = row[2];
764                                 if( row[2] > maxima_b ) maxima_b = row[2];
765                         }
766                 }
767
768                 // Calculate postprocessing coeficents
769                 values.coef2 = minima_r;
770                 if( minima_g < values.coef2 ) values.coef2 = minima_g;
771                 if( minima_b < values.coef2 ) values.coef2 = minima_b;
772                 values.coef1 = maxima_r;
773                 if( maxima_g > values.coef1 ) values.coef1 = maxima_g;
774                 if( maxima_b > values.coef1 ) values.coef1 = maxima_b;
775                 // Try not to overflow RGB601 (235 - 16) / 256 * 0.9
776                 float den = values.coef1 - values.coef2;
777                 if( fabs(den) < 1e-6 ) den = 1e-6;
778                 values.coef1 = 0.770 / den;
779                 values.coef2 = 0.065 - values.coef2 * values.coef1;
780                 send_render_gui(&values);
781         }
782
783         // Apply the transformation
784         if( config.active ) {
785                 for( int i = 0; i < frame_h; i++ ) {
786                         float *row = (float*)frame->get_rows()[i];
787                         for( int j = 0; j < frame_w; j++, row += pix_len ) {
788                                 pix_fix(row[0], config.fix_min_r, 1);
789                                 pix_fix(row[1], config.fix_min_g, config.fix_gamma_g);
790                                 pix_fix(row[2], config.fix_min_b, config.fix_gamma_b);
791                         }
792                 }
793         }
794
795         if( config.show_box ) {
796                 EDLSession *session = get_edlsession();
797                 int line_w = bmax(session->output_w,session->output_h) / 600 + 1;
798                 for( int j=0; j<line_w; ++j ) {
799                         float **rows = (float **)frame->get_rows();
800                         if( min_row < max_row - 1 ) {
801                                 float *row1 = (float *)rows[min_row+j];
802                                 float *row2 = (float *)rows[max_row-j - 1];
803
804                                 for( int i = 0; i < frame_w; i++ ) {
805                                         for( int j = 0; j < 3; j++ ) {
806                                                 row1[j] = pix_max - row1[j];
807                                                 row2[j] = pix_max - row2[j];
808                                         }
809                                         if( has_alpha ) {
810                                                 row1[3] = pix_max;
811                                                 row2[3] = pix_max;
812                                         }
813                                         row1 += pix_len;
814                                         row2 += pix_len;
815                                 }
816                         }
817
818                         if( min_col < max_col - 1 ) {
819                                 int pix1 = pix_len * min_col+j;
820                                 int pix2 = pix_len * (max_col-j - 1);
821
822                                 for( int i = 0; i < frame_h; i++ ) {
823                                         float *row1 = rows[i] + pix1;
824                                         float *row2 = rows[i] + pix2;
825
826                                         for( int j = 0; j < 3; j++ ) {
827                                                 row1[j] = pix_max - row1[j];
828                                                 row2[j] = pix_max - row2[j];
829                                         }
830                                         if( has_alpha ) {
831                                                 row1[3] = pix_max;
832                                                 row2[3] = pix_max;
833                                         }
834                                 }
835                         }
836                 }
837         }
838
839         if( frame != output )
840                 output->transfer_from(frame);
841         if( frame == input )
842                 frame = 0;
843
844         return 0;
845 }
846