gif rework, C41 booby fix, add ext+s for list seq, features5
[goodguy/cinelerra.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 "bccolors.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 N_("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 }
231
232 void C41Window::create_objects()
233 {
234         int x = 10, y = 10;
235         C41Effect *plugin = (C41Effect *)client;
236         add_subwindow(active = new C41Enable(plugin,
237                 &plugin->config.active, x, y, _("Activate processing")));
238         y += 40;
239
240         add_subwindow(compute_magic = new C41Enable(plugin,
241                 &plugin->config.compute_magic, x, y, _("Compute negfix values")));
242         y += 20;
243
244         add_subwindow(new BC_Title(x + 20, y, _("(uncheck for faster rendering)")));
245         y += 40;
246
247         add_subwindow(new BC_Title(x, y, _("Computed negfix values:")));
248         y += 30;
249
250         add_subwindow(new BC_Title(x, y, _("Min/Max R:")));
251         add_subwindow(min_r = new BC_Title(x + 80, y, "0.0000 / 0.0000"));
252         y += 30;
253
254         add_subwindow(new BC_Title(x, y, _("Min/Max G:")));
255         add_subwindow(min_g = new BC_Title(x + 80, y, "0.0000 / 0.0000"));
256         y += 30;
257
258         add_subwindow(new BC_Title(x, y, _("Min/Max B:")));
259         add_subwindow(min_b = new BC_Title(x + 80, y, "0.0000 / 0.0000"));
260         y += 30;
261
262         add_subwindow(new BC_Title(x, y, _("Light:")));
263         add_subwindow(light = new BC_Title(x + 80, y, "0.0000"));
264         y += 30;
265
266         add_subwindow(new BC_Title(x, y, _("Gamma G:")));
267         add_subwindow(gamma_g = new BC_Title(x + 80, y, "0.0000"));
268         y += 30;
269
270         add_subwindow(new BC_Title(x, y, _("Gamma B:")));
271         add_subwindow(gamma_b = new BC_Title(x + 80, y, "0.0000"));
272         y += 30;
273
274         add_subwindow(new BC_Title(x, y, _("Contrast:")));
275         add_subwindow(coef1 = new BC_Title(x + 80, y, "0.0000"));
276         y += 30;
277
278         add_subwindow(new BC_Title(x, y, _("Brightness:")));
279         add_subwindow(coef2 = new BC_Title(x + 80, y, "0.0000"));
280         y += 30;
281
282         add_subwindow(lock = new C41Button(plugin, this, x, y));
283         y += 30;
284
285 #define BOX_COL 120
286         add_subwindow(new BC_Title(x, y, _("Box col:")));
287         add_subwindow(box_col_min = new BC_Title(x + 80, y, "0"));
288         add_subwindow(box_col_max = new BC_Title(x + BOX_COL, y, "0"));
289         y += 30;
290
291         add_subwindow(new BC_Title(x, y, _("Box row:")));
292         add_subwindow(box_row_min = new BC_Title(x + 80, y, "0"));
293         add_subwindow(box_row_max = new BC_Title(x + BOX_COL, y, "0"));
294         y += 30;
295
296         add_subwindow(boxlock = new C41BoxButton(plugin, this, x, y));
297
298         y = 10;
299         x = 250;
300         add_subwindow(show_box = new C41Enable(plugin,
301                 &plugin->config.show_box, x, y, _("Show active area")));
302         y += 40;
303
304         add_subwindow(postproc = new C41Enable(plugin,
305                 &plugin->config.postproc, x, y, _("Postprocess")));
306         y += 60;
307
308         add_subwindow(new BC_Title(x, y, _("negfix values to apply:")));
309         y += 30;
310
311         add_subwindow(new BC_Title(x, y, _("Min R:")));
312         add_subwindow(fix_min_r = new C41TextBox(plugin,
313                 &plugin->config.fix_min_r, x + 80, y));
314         y += 30;
315
316         add_subwindow(new BC_Title(x, y, _("Min G:")));
317         add_subwindow(fix_min_g = new C41TextBox(plugin,
318                 &plugin->config.fix_min_g, x + 80, y));
319         y += 30;
320
321         add_subwindow(new BC_Title(x, y, _("Min B:")));
322         add_subwindow(fix_min_b = new C41TextBox(plugin,
323                 &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,
328                  &plugin->config.fix_light, x + 80, y));
329         y += 30;
330
331         add_subwindow(new BC_Title(x, y, _("Gamma G:")));
332         add_subwindow(fix_gamma_g = new C41TextBox(plugin,
333                  &plugin->config.fix_gamma_g, x + 80, y));
334         y += 30;
335
336         add_subwindow(new BC_Title(x, y, _("Gamma B:")));
337         add_subwindow(fix_gamma_b = new C41TextBox(plugin,
338                  &plugin->config.fix_gamma_b, x + 80, y));
339         y += 30;
340
341         add_subwindow(new BC_Title(x, y, _("Contrast:")));
342         add_subwindow(fix_coef1 = new C41TextBox(plugin,
343                  &plugin->config.fix_coef1, x + 80, y));
344         y += 30;
345
346         add_subwindow(new BC_Title(x, y, _("Brightness:")));
347         add_subwindow(fix_coef2 = new C41TextBox(plugin,
348                  &plugin->config.fix_coef2, x + 80, y));
349         y += 30;
350
351         x += 40;
352         add_subwindow(new BC_Title(x - 40, y, _("Col:")));
353         add_subwindow(min_col = new C41Slider(plugin,
354                  &plugin->config.min_col, x, y, 0));
355         y += 25;
356
357         add_subwindow(max_col = new C41Slider(plugin,
358                  &plugin->config.max_col, x, y, 0));
359         y += 25;
360
361         add_subwindow(new BC_Title(x - 40, y, _("Row:")));
362         add_subwindow(min_row = new C41Slider(plugin,
363                  &plugin->config.min_row, x, y, 1));
364         y += 25;
365
366         add_subwindow(max_row = new C41Slider(plugin,
367                  &plugin->config.max_row, x, y, 1));
368         y += 25;
369
370         update_magic();
371         show_window(1);
372 }
373
374 void C41Window::update()
375 {
376         C41Effect *plugin = (C41Effect *)client;
377         // Updating the GUI itself
378         active->update(plugin->config.active);
379         compute_magic->update(plugin->config.compute_magic);
380         postproc->update(plugin->config.postproc);
381         show_box->update(plugin->config.show_box);
382
383         fix_min_r->update(plugin->config.fix_min_r);
384         fix_min_g->update(plugin->config.fix_min_g);
385         fix_min_b->update(plugin->config.fix_min_b);
386         fix_light->update(plugin->config.fix_light);
387         fix_gamma_g->update(plugin->config.fix_gamma_g);
388         fix_gamma_b->update(plugin->config.fix_gamma_b);
389         fix_coef1->update(plugin->config.fix_coef1);
390         fix_coef2->update(plugin->config.fix_coef2);
391
392         min_row->update(plugin->config.min_row);
393         max_row->update(plugin->config.max_row);
394         min_col->update(plugin->config.min_col);
395         max_col->update(plugin->config.max_col);
396
397         update_magic();
398 }
399
400 void C41Window::update_magic()
401 {
402         C41Effect *plugin = (C41Effect *)client;
403         char text[BCSTRLEN];
404         sprintf(text, "%0.4f / %0.4f", plugin->values.min_r, plugin->values.max_r);
405         min_r->update(text);
406         sprintf(text, "%0.4f / %0.4f", plugin->values.min_g, plugin->values.max_g);
407         min_g->update(text);
408         sprintf(text, "%0.4f / %0.4f", plugin->values.min_b, plugin->values.max_b);
409         min_b->update(text);
410         light->update(plugin->values.light);
411         gamma_g->update(plugin->values.gamma_g);
412         gamma_b->update(plugin->values.gamma_b);
413         // Avoid blinking
414         if( plugin->values.coef1 > 0 || plugin->values.coef2 > 0 ) {
415                 coef1->update(plugin->values.coef1);
416                 coef2->update(plugin->values.coef2);
417         }
418         sprintf(text, "%d", plugin->values.shave_min_col);
419         box_col_min->update(text);
420         sprintf(text, "%d", plugin->values.shave_max_col);
421         box_col_max->update(text);
422         sprintf(text, "%d", plugin->values.shave_min_row);
423         box_row_min->update(text);
424         sprintf(text, "%d", plugin->values.shave_max_row);
425         box_row_max->update(text);
426 }
427
428
429 // C41Effect
430 C41Effect::C41Effect(PluginServer *server)
431 : PluginVClient(server)
432 {
433         frame = 0;
434         tmp_frame = 0;
435         blurry_frame = 0;
436         memset(&values, 0, sizeof(values));
437         shave_min_row = shave_max_row = 0;
438         shave_min_col = shave_max_col = 0;
439         min_col = max_col = 0;
440         min_row = max_row = 0;
441         pix_max = 0;  pix_len = 0;
442 }
443
444 C41Effect::~C41Effect()
445 {
446         delete frame;
447         delete tmp_frame;
448         delete blurry_frame;
449 }
450
451 void C41Effect::render_gui(void* data)
452 {
453         // Updating values computed by process_frame
454         struct magic *vp = (struct magic *)data;
455         values = *vp;
456
457         if( thread ) {
458                 C41Window *window = (C41Window *) thread->window;
459                 window->lock_window("C41Effect::render_gui");
460                 window->update_magic();
461                 window->unlock_window();
462         }
463 }
464
465 void C41Effect::save_data(KeyFrame *keyframe)
466 {
467         FileXML output;
468         output.set_shared_output(keyframe->xbuf);
469
470         output.tag.set_title("C41");
471         output.tag.set_property("ACTIVE", config.active);
472         output.tag.set_property("COMPUTE_MAGIC", config.compute_magic);
473         output.tag.set_property("POSTPROC", config.postproc);
474         output.tag.set_property("SHOW_BOX", config.show_box);
475
476         output.tag.set_property("FIX_MIN_R", config.fix_min_r);
477         output.tag.set_property("FIX_MIN_G", config.fix_min_g);
478         output.tag.set_property("FIX_MIN_B", config.fix_min_b);
479         output.tag.set_property("FIX_LIGHT", config.fix_light);
480         output.tag.set_property("FIX_GAMMA_G", config.fix_gamma_g);
481         output.tag.set_property("FIX_GAMMA_B", config.fix_gamma_b);
482         output.tag.set_property("FIX_COEF1", config.fix_coef1);
483         output.tag.set_property("FIX_COEF2", config.fix_coef2);
484         output.tag.set_property("MIN_ROW", config.min_row);
485         output.tag.set_property("MAX_ROW", config.max_row);
486         output.tag.set_property("MIN_COL", config.min_col);
487         output.tag.set_property("MAX_COL", config.max_col);
488         output.append_tag();
489         output.tag.set_title("/C41");
490         output.append_tag();
491         output.terminate_string();
492 }
493
494 void C41Effect::read_data(KeyFrame *keyframe)
495 {
496         FileXML input;
497         input.set_shared_input(keyframe->xbuf);
498
499         while(!input.read_tag())
500         {
501                 if( input.tag.title_is("C41") ) {
502                         config.active = input.tag.get_property("ACTIVE", config.active);
503                         config.compute_magic = input.tag.get_property("COMPUTE_MAGIC", config.compute_magic);
504                         config.postproc = input.tag.get_property("POSTPROC", config.postproc);
505                         config.show_box = input.tag.get_property("SHOW_BOX", config.show_box);
506                         config.fix_min_r = input.tag.get_property("FIX_MIN_R", config.fix_min_r);
507                         config.fix_min_g = input.tag.get_property("FIX_MIN_G", config.fix_min_g);
508                         config.fix_min_b = input.tag.get_property("FIX_MIN_B", config.fix_min_b);
509                         config.fix_light = input.tag.get_property("FIX_LIGHT", config.fix_light);
510                         config.fix_gamma_g = input.tag.get_property("FIX_GAMMA_G", config.fix_gamma_g);
511                         config.fix_gamma_b = input.tag.get_property("FIX_GAMMA_B", config.fix_gamma_b);
512                         config.fix_coef1 = input.tag.get_property("FIX_COEF1", config.fix_coef2);
513                         config.fix_coef2 = input.tag.get_property("FIX_COEF2", config.fix_coef2);
514                         config.min_row = input.tag.get_property("MIN_ROW", config.min_row);
515                         config.max_row = input.tag.get_property("MAX_ROW", config.max_row);
516                         config.min_col = input.tag.get_property("MIN_COL", config.min_col);
517                         config.max_col = input.tag.get_property("MAX_COL", config.max_col);
518                 }
519         }
520 }
521
522 #if defined(C41_FAST_POW)
523
524 float C41Effect::myLog2(float i)
525 {
526         float x, y;
527         float LogBodge = 0.346607f;
528         x = *(int *)&i;
529         x *= 1.0 / (1 << 23); // 1/pow(2,23);
530         x = x - 127;
531
532         y = x - floorf(x);
533         y = (y - y * y) * LogBodge;
534         return x + y;
535 }
536
537 float C41Effect::myPow2(float i)
538 {
539         float PowBodge = 0.33971f;
540         float x;
541         float y = i - floorf(i);
542         y = (y - y * y) * PowBodge;
543
544         x = i + 127 - y;
545         x *= (1 << 23);
546         *(int*)&x = (int)x;
547         return x;
548 }
549
550 float C41Effect::myPow(float a, float b)
551 {
552         return myPow2(b * myLog2(a));
553 }
554
555 #endif
556
557 void C41Effect::pix_fix(float &pix, float min, float gamma)
558 {
559         float val = pix;
560         if( val >= 1e-3 ) {
561                 val = min / val;
562                 if( gamma != 1 ) val = POWF(val, gamma);
563                 bclamp(val -= config.fix_light, 0, pix_max);
564                 if( config.postproc )
565                         val = config.fix_coef1 * val + config.fix_coef2;
566         }
567         else
568                 val = 1;
569         pix = val;
570 }
571
572 int C41Effect::process_realtime(VFrame *input, VFrame *output)
573 {
574         load_configuration();
575
576         int frame_w = input->get_w(), frame_h = input->get_h();
577         int input_model = input->get_color_model();
578         int has_alpha = BC_CModels::has_alpha(input_model);
579         int color_model = has_alpha ? BC_RGBA_FLOAT : BC_RGB_FLOAT;
580
581         if( input_model != color_model ) {
582                 if( frame &&
583                     ( frame->get_w() != frame_w || frame->get_h() != frame_h ||
584                       frame->get_color_model() != color_model ) ) {
585                         delete frame;  frame = 0;
586                 }
587                 if( !frame )
588                         frame = new VFrame(frame_w, frame_h, color_model);
589                 frame->transfer_from(input);
590         }
591         else {
592                 delete frame;
593                 frame = input;
594         }
595
596         pix_max = BC_CModels::calculate_max(color_model);
597         pix_len = BC_CModels::components(color_model);
598
599         min_row = config.min_row;  bclamp(min_row, 0,frame_h);
600         max_row = config.max_row;  bclamp(max_row, 0,frame_h);
601         min_col = config.min_col;  bclamp(min_col, 0,frame_w);
602         max_col = config.max_col;  bclamp(max_col, 0,frame_w);
603
604         if( config.compute_magic ) {
605                 // Convert frame to RGB for magic computing
606                 if( tmp_frame &&
607                     (tmp_frame->get_w() != frame_w || tmp_frame->get_h() != frame_h) ) {
608                         delete tmp_frame;  tmp_frame = 0;
609                 }
610                 if( !tmp_frame )
611                         tmp_frame = new VFrame(frame_w, frame_h, BC_RGB_FLOAT);
612                 if( blurry_frame &&
613                     (blurry_frame->get_w() != frame_w || blurry_frame->get_h() != frame_h) ) {
614                         delete blurry_frame;  blurry_frame = 0;
615                 }
616                 if( !blurry_frame )
617                         blurry_frame = new VFrame(frame_w, frame_h, BC_RGB_FLOAT);
618
619                 float **rows = (float **)frame->get_rows();
620                 float **tmp_rows = (float **)tmp_frame->get_rows();
621                 float **blurry_rows = (float **)blurry_frame->get_rows();
622
623                 for( int i=0; i<frame_h; ++i ) {
624                         float *row = rows[i], *tmp = tmp_rows[i];
625                         for( int j=frame_w; --j>=0; row+=pix_len ) {
626                                 *tmp++ = bclip(row[0], 0, pix_max);
627                                 *tmp++ = bclip(row[1], 0, pix_max);
628                                 *tmp++ = bclip(row[2], 0, pix_max);
629                         }
630                 }
631
632                 // 10 passes of Box blur should be good
633                 int dx = 5, dy = 5, y_up, y_dn, x_rt, x_lt;
634                 float **src = tmp_rows, **tgt = blurry_rows;
635                 for( int pass = 0; pass < 10; pass++ ) {
636                         for( int y = 0; y < frame_h; y++ ) {
637                                 if( (y_up=y-dy) < 0 ) y_up = 0;
638                                 if( (y_dn=y+dy) >= frame_h ) y_dn = frame_h-1;
639                                 float *up = src[y_up], *dn = src[y_dn], *row = tgt[y];
640                                 for( int x = 0; x < frame_w; ++x ) {
641                                         if( (x_lt=x-dx) < 0 ) x_lt = 0;
642                                         if( (x_rt=x+dx) >= frame_w ) x_lt = frame_w-1;
643                                         float *dn_l = dn + 3*x_lt, *dn_r = dn + 3*x_rt;
644                                         float *up_l = up + 3*x_lt, *up_r = up + 3*x_rt;
645                                         row[0] = (dn_l[0] + dn_r[0] + up_l[0] + up_r[0]) / 4;
646                                         row[1] = (dn_l[1] + dn_r[1] + up_l[1] + up_r[1]) / 4;
647                                         row[2] = (dn_l[2] + dn_r[2] + up_l[2] + up_r[2]) / 4;
648                                         row += 3;
649                                 }
650                         }
651                         float **rows = src;  src = tgt;  tgt = rows;
652                 }
653
654                 // Shave image: cut off border areas where min max difference
655                 // is less than C41_SHAVE_TOLERANCE, starting/ending at C41_SHAVE_MARGIN
656
657                 shave_min_row = shave_min_col = 0;
658                 shave_max_col = frame_w;
659                 shave_max_row = frame_h;
660                 int min_w = frame_w * C41_SHAVE_MARGIN;
661                 int max_w = frame_w * (1-C41_SHAVE_MARGIN);
662                 int min_h = frame_h * C41_SHAVE_MARGIN;
663                 int max_h = frame_h * (1-C41_SHAVE_MARGIN);
664
665                 float yv_min[frame_h], yv_max[frame_h];
666                 for( int y=0; y<frame_h; ++y ) {
667                         yv_min[y] = pix_max;
668                         yv_max[y] = 0.;
669                 }
670
671                 for( int y = 0; y < frame_h; y++ ) {
672                         float *row = blurry_rows[y];
673                         for( int x = 0; x < frame_w; x++, row += 3 ) {
674                                 float yv = (row[0] + row[1] + row[2]) / 3;
675                                 if( yv_min[y] > yv ) yv_min[y] = yv;
676                                 if( yv_max[y] < yv ) yv_max[y] = yv;
677                         }
678                 }
679
680                 for( shave_min_row = min_h; shave_min_row < max_h; shave_min_row++ )
681                         if( yv_max[shave_min_row] - yv_min[shave_min_row] > C41_SHAVE_TOLERANCE )
682                                 break;
683                 for( shave_max_row = max_h - 1; shave_max_row > shave_min_row; shave_max_row-- )
684                         if( yv_max[shave_max_row] - yv_min[shave_max_row] > C41_SHAVE_TOLERANCE )
685                                 break;
686                 shave_max_row++;
687
688                 float xv_min[frame_w], xv_max[frame_w];
689                 for( int x=0; x<frame_w; ++x ) {
690                         xv_min[x] = pix_max;
691                         xv_max[x] = 0.;
692                 }
693
694                 for( int y = shave_min_row; y < shave_max_row; y++ ) {
695                         float *row = blurry_rows[y];
696                         for( int x = 0; x < frame_w; x++, row += 3 ) {
697                                 float xv = (row[0] + row[1] + row[2]) / 3;
698                                 if( xv_min[x] > xv ) xv_min[x] = xv;
699                                 if( xv_max[x] < xv ) xv_max[x] = xv;
700                         }
701                 }
702
703                 for( shave_min_col = min_w; shave_min_col < max_w; shave_min_col++ )
704                         if( xv_max[shave_min_col] - xv_min[shave_min_col] > C41_SHAVE_TOLERANCE )
705                                 break;
706                 for( shave_max_col = max_w - 1; shave_max_col > shave_min_col; shave_max_col-- )
707                         if( xv_max[shave_max_col] - xv_min[shave_max_col] > C41_SHAVE_TOLERANCE )
708                                 break;
709                 shave_max_col++;
710
711                 // Compute magic negfix values
712                 float minima_r = 50., minima_g = 50., minima_b = 50.;
713                 float maxima_r = 0., maxima_g = 0., maxima_b = 0.;
714
715                 // Check if config_parameters are usable
716                 if( config.min_col >= config.max_col || config.min_row >= config.max_row ) {
717                         min_col = shave_min_col;
718                         max_col = shave_max_col;
719                         min_row = shave_min_row;
720                         max_row = shave_max_row;
721                 }
722
723                 for( int i = min_row; i < max_row; i++ ) {
724                         float *row = blurry_rows[i] + 3 * min_col;
725                         for( int j = min_col; j < max_col; j++, row+=3 ) {
726                                 if( row[0] < minima_r ) minima_r = row[0];
727                                 if( row[0] > maxima_r ) maxima_r = row[0];
728                                 if( row[1] < minima_g ) minima_g = row[1];
729                                 if( row[1] > maxima_g ) maxima_g = row[1];
730                                 if( row[2] < minima_b ) minima_b = row[2];
731                                 if( row[2] > maxima_b ) maxima_b = row[2];
732                         }
733                 }
734                 values.min_r = minima_r;  values.max_r = maxima_r;
735                 values.min_g = minima_g;  values.max_g = maxima_g;
736                 values.min_b = minima_b;  values.max_b = maxima_b;
737                 values.light = maxima_r < 1e-6 ? 1.0 : minima_r / maxima_r;
738                 bclamp(minima_r, 1e-6, 1-1e-6);  bclamp(maxima_r, 1e-6, 1-1e-6);
739                 bclamp(minima_g, 1e-6, 1-1e-6);  bclamp(maxima_g, 1e-6, 1-1e-6);
740                 bclamp(minima_b, 1e-6, 1-1e-6);  bclamp(maxima_b, 1e-6, 1-1e-6);
741                 float log_r = logf(maxima_r / minima_r);
742                 float log_g = logf(maxima_g / minima_g);
743                 float log_b = logf(maxima_b / minima_b);
744                 if( log_g < 1e-6 ) log_g = 1e-6;
745                 if( log_b < 1e-6 ) log_b = 1e-6;
746                 values.gamma_g = log_r / log_g;
747                 values.gamma_b = log_r / log_b;
748                 values.shave_min_col = shave_min_col;
749                 values.shave_max_col = shave_max_col;
750                 values.shave_min_row = shave_min_row;
751                 values.shave_max_row = shave_max_row;
752                 values.coef1 = values.coef2 = -1;
753
754                 // Update GUI
755                 send_render_gui(&values);
756         }
757
758         if( config.compute_magic ) {
759                 float minima_r = 50., minima_g = 50., minima_b = 50.;
760                 float maxima_r = 0., maxima_g = 0., maxima_b = 0.;
761
762                 for( int i = min_row; i < max_row; i++ ) {
763                         float *row = (float*)frame->get_rows()[i];
764                         row += 3 * min_col;
765                         for( int j = min_col; j < max_col; j++, row += 3 ) {
766                                 if( row[0] < minima_r ) minima_r = row[0];
767                                 if( row[0] > maxima_r ) maxima_r = row[0];
768
769                                 if( row[1] < minima_g ) minima_g = row[1];
770                                 if( row[1] > maxima_g ) maxima_g = row[1];
771
772                                 if( row[2] < minima_b ) minima_b = row[2];
773                                 if( row[2] > maxima_b ) maxima_b = row[2];
774                         }
775                 }
776
777                 // Calculate postprocessing coeficents
778                 values.coef2 = minima_r;
779                 if( minima_g < values.coef2 ) values.coef2 = minima_g;
780                 if( minima_b < values.coef2 ) values.coef2 = minima_b;
781                 values.coef1 = maxima_r;
782                 if( maxima_g > values.coef1 ) values.coef1 = maxima_g;
783                 if( maxima_b > values.coef1 ) values.coef1 = maxima_b;
784                 // Try not to overflow RGB601 (235 - 16) / 256 * 0.9
785                 float den = values.coef1 - values.coef2;
786                 if( fabs(den) < 1e-6 ) den = 1e-6;
787                 values.coef1 = 0.770 / den;
788                 values.coef2 = 0.065 - values.coef2 * values.coef1;
789                 send_render_gui(&values);
790         }
791
792         // Apply the transformation
793         if( config.active ) {
794                 for( int i = 0; i < frame_h; i++ ) {
795                         float *row = (float*)frame->get_rows()[i];
796                         for( int j = 0; j < frame_w; j++, row += pix_len ) {
797                                 pix_fix(row[0], config.fix_min_r, 1);
798                                 pix_fix(row[1], config.fix_min_g, config.fix_gamma_g);
799                                 pix_fix(row[2], config.fix_min_b, config.fix_gamma_b);
800                         }
801                 }
802         }
803
804         if( config.show_box ) {
805                 EDLSession *session = get_edlsession();
806                 int line_w = bmax(session->output_w,session->output_h) / 600 + 1;
807                 for( int k=0; k<line_w; ++k ) {
808                         float **rows = (float **)frame->get_rows();
809                         if( min_row < max_row - 1 ) {
810                                 float *row1 = (float *)rows[min_row+k];
811                                 float *row2 = (float *)rows[max_row-k - 1];
812
813                                 for( int i = 0; i < frame_w; i++ ) {
814                                         for( int j = 0; j < 3; j++ ) {
815                                                 row1[j] = pix_max - row1[j];
816                                                 row2[j] = pix_max - row2[j];
817                                         }
818                                         if( has_alpha ) {
819                                                 row1[3] = pix_max;
820                                                 row2[3] = pix_max;
821                                         }
822                                         row1 += pix_len;
823                                         row2 += pix_len;
824                                 }
825                         }
826
827                         if( min_col < max_col - 1 ) {
828                                 int pix1 = pix_len * (min_col+k);
829                                 int pix2 = pix_len * (max_col-k - 1);
830
831                                 for( int i = 0; i < frame_h; i++ ) {
832                                         float *row1 = rows[i] + pix1;
833                                         float *row2 = rows[i] + pix2;
834
835                                         for( int j = 0; j < 3; j++ ) {
836                                                 row1[j] = pix_max - row1[j];
837                                                 row2[j] = pix_max - row2[j];
838                                         }
839                                         if( has_alpha ) {
840                                                 row1[3] = pix_max;
841                                                 row2[3] = pix_max;
842                                         }
843                                 }
844                         }
845                 }
846         }
847
848         if( frame != output )
849                 output->transfer_from(frame);
850         if( frame == input )
851                 frame = 0;
852
853         return 0;
854 }
855