4 * Copyright (C) 2020 William Morrow
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
30 #include "loadbalance.h"
38 REGISTER_PLUGIN(HistEqMain)
40 HistEqConfig::HistEqConfig()
48 HistEqConfig::~HistEqConfig()
52 void HistEqConfig::copy_from(HistEqConfig &that)
60 int HistEqConfig::equivalent(HistEqConfig &that)
65 void HistEqConfig::interpolate(HistEqConfig &prev, HistEqConfig &next,
66 int64_t prev_frame, int64_t next_frame, int64_t current_frame)
72 HistEqWindow::HistEqWindow(HistEqMain *plugin)
73 : PluginClientWindow(plugin, plugin->w, plugin->h, plugin->w, plugin->h, 0)
75 this->plugin = plugin;
78 HistEqWindow::~HistEqWindow()
83 void HistEqWindow::create_objects()
85 int xs10 = xS(10), xs60 = xS(60);
87 int x = xs10, y = ys10;
88 int cw = get_w()-2*x, ch = cw*3/4;
89 add_subwindow(canvas = new HistEqCanvas(this, plugin, x, y, cw, ch));
90 y += canvas->get_h() + ys10;
92 add_subwindow(split = new HistEqSplit(this, plugin, x, y));
93 y += split->get_h() + ys10;
95 add_subwindow(plot = new HistEqPlot(this, plugin, x, y));
96 y += plot->get_h() + ys10;
99 add_subwindow(new BC_Title(x, y, _("Blend:")));
100 add_subwindow(blend = new HistEqBlend(this, plugin, x1, y));
101 y += blend->get_h() + ys10;
103 add_subwindow(new BC_Title(x, y, _("Gain:")));
104 add_subwindow(gain = new HistEqGain(this, plugin, x1, y));
105 y += gain->get_h() + ys10;
110 void HistEqWindow::update()
114 HistEqCanvas::HistEqCanvas(HistEqWindow *gui, HistEqMain *plugin,
115 int x, int y, int w, int h)
116 : BC_SubWindow(x, y, w, h, BLACK)
119 this->plugin = plugin;
121 void HistEqCanvas::clear()
123 clear_box(0,0, get_w(),get_h());
126 void HistEqCanvas::draw_bins(HistEqMain *plugin)
129 int *data = plugin->bins;
130 int n = plugin->binsz, max = 0;
131 for( int i=0; i<n; ++i )
132 if( max < data[i] ) max = data[i];
133 double lmax = log(max);
134 int cw = get_w(), ch = get_h();
138 int x1 = (n * ++x) / cw;
139 for( int i=x0; ++i<x1; ) {
143 double lmx = mx>0 ? log(mx) : 0;
144 int y1 = ch * (1 - lmx/lmax);
145 draw_line(x,0, x,y1);
150 void HistEqCanvas::draw_wts(HistEqMain *plugin)
152 float *wts = plugin->wts;
153 int n = plugin->binsz;
155 int cw1 = get_w()-1, ch1 = get_h()-1;
156 float g0 = plugin->config.gain, g1 = 1-g0;
157 int x1 = 0, y1 = ch1;
159 int x0 = x1++, y0 = y1;
160 int is = (n * x1) / cw1;
161 float fy = wts[is]*g0 + ((float)x1/cw1)*g1;
163 draw_line(x0,y0, x1,y1);
167 void HistEqCanvas::draw_reg(HistEqMain *plugin)
170 int cw1 = get_w()-1, ch1 = get_h()-1;
171 float g0 = plugin->config.gain, g1 = 1-g0;
172 int x0 = 0, x1 = plugin->binsz-1;
173 double a = plugin->a, b = plugin->b;
174 float fy0 = (a*x0 + b)*g0 + 0*g1;
175 float fy1 = (a*x1 + b)*g0 + 1*g1;
176 int y0 = (1 - fy0) * ch1;
177 int y1 = (1 - fy1) * ch1;
178 draw_line(0,y0, cw1,y1);
181 void HistEqCanvas::draw_lut(HistEqMain *plugin)
183 int *lut = plugin->lut;
184 int n = plugin->lutsz-1;
187 int cw1 = get_w()-1, ch1 = get_h()-1;
188 int x1 = 0, y1 = ch1;
190 int x0 = x1++, y0 = y1;
191 int is = (n * x1) / cw1;
192 y1 = (1-s*lut[is]) * ch1;
193 draw_line(x0,y0, x1,y1);
197 void HistEqCanvas::update(HistEqMain *plugin)
207 HistEqSplit::HistEqSplit(HistEqWindow *gui, HistEqMain *plugin, int x, int y)
208 : BC_CheckBox(x, y, plugin->config.split, _("Split output"))
211 this->plugin = plugin;
213 HistEqSplit::~HistEqSplit()
217 int HistEqSplit::handle_event()
219 plugin->config.split = get_value();
220 plugin->send_configure_change();
224 HistEqPlot::HistEqPlot(HistEqWindow *gui, HistEqMain *plugin, int x, int y)
225 : BC_CheckBox(x, y, plugin->config.plot, _("Plot bins/lut"))
228 this->plugin = plugin;
230 HistEqPlot::~HistEqPlot()
234 int HistEqPlot::handle_event()
236 plugin->config.plot = get_value();
237 plugin->send_configure_change();
241 HistEqBlend::HistEqBlend(HistEqWindow *gui, HistEqMain *plugin, int x, int y)
242 : BC_FSlider(x, y, 0, xS(150), yS(200), 0, 1.0, plugin->config.blend, 0)
245 this->plugin = plugin;
248 HistEqBlend::~HistEqBlend()
252 int HistEqBlend::handle_event()
254 plugin->config.blend = get_value();
255 plugin->send_configure_change();
260 HistEqGain::HistEqGain(HistEqWindow *gui, HistEqMain *plugin, int x, int y)
261 : BC_FSlider(x, y, 0, xS(150), yS(200), 0, 1.0, plugin->config.gain, 0)
264 this->plugin = plugin;
267 HistEqGain::~HistEqGain()
271 int HistEqGain::handle_event()
273 plugin->config.gain = get_value();
274 plugin->send_configure_change();
279 HistEqMain::HistEqMain(PluginServer *server)
280 : PluginVClient(server)
282 w = xS(300); h = yS(375);
285 binsz = bsz = 0; bins = 0;
286 lutsz = lsz = 0; lut = 0;
291 HistEqMain::~HistEqMain()
299 const char* HistEqMain::plugin_title() { return N_("HistEq"); }
300 int HistEqMain::is_realtime() { return 1; }
303 NEW_WINDOW_MACRO(HistEqMain, HistEqWindow)
305 LOAD_CONFIGURATION_MACRO(HistEqMain, HistEqConfig)
307 void HistEqMain::update_gui()
309 if( !thread ) return;
310 if( !load_configuration() ) return;
311 ((HistEqWindow*)thread->window)->lock_window("HistEqMain::update_gui");
312 HistEqWindow* gui = (HistEqWindow*)thread->window;
314 gui->unlock_window();
317 void HistEqMain::save_data(KeyFrame *keyframe)
320 output.set_shared_output(keyframe->xbuf);
321 output.tag.set_title("HISTEQ");
322 output.tag.set_property("W", w);
323 output.tag.set_property("H", h);
324 output.tag.set_property("SPLIT", config.split);
325 output.tag.set_property("PLOT", config.plot);
326 output.tag.set_property("BLEND", config.blend);
327 output.tag.set_property("GAIN", config.gain);
329 output.tag.set_title("/HISTEQ");
331 output.append_newline();
332 output.terminate_string();
335 void HistEqMain::read_data(KeyFrame *keyframe)
338 input.set_shared_input(keyframe->xbuf);
340 while( !(result=input.read_tag()) ) {
341 if( input.tag.title_is("HISTEQ") ) {
343 w = input.tag.get_property("W", w);
344 h = input.tag.get_property("H", h);
346 config.split = input.tag.get_property("SPLIT", config.split);
347 config.plot = input.tag.get_property("PLOT", config.plot);
348 config.blend = input.tag.get_property("BLEND", config.blend);
349 config.gain = input.tag.get_property("GAIN", config.gain);
354 void HistEqMain::render_gui(void *data)
356 if( !thread ) return;
357 ((HistEqWindow*)thread->window)->lock_window("HistEqMain::render_gui 1");
358 HistEqWindow *gui = (HistEqWindow*)thread->window;
359 gui->canvas->update((HistEqMain *)data);
360 gui->unlock_window();
364 static void fit(float *dat, int n, double &a, double &b)
367 for( int i=0; i<n; ++i ) sy += dat[i];
368 double s = 0, mx = (n-1)/2., my = sy / n;
369 for( int i=0; i<n; ++i ) s += (i-mx) * dat[i];
371 double ss = 2. * ((m+1)*m*(m-1)/3. + m*(m-1)/2.);
376 int HistEqMain::process_buffer(VFrame *frame, int64_t start_position, double frame_rate)
378 load_configuration();
379 read_frame(frame, 0, start_position, frame_rate, 0);
380 this->input = this->output = frame;
382 int colormodel = frame->get_color_model();
384 binsz = (3*0xffff) + 1;
385 switch( colormodel ) {
388 binsz = (3*0xff) + 1;
393 binsz = (3*0x5555) + 1;
396 if( binsz != bsz ) { delete bins; bins = 0; bsz = 0; }
397 if( !bins ) bins = new int[bsz = binsz];
398 if( binsz+1 != wsz ) { delete wts; wts = 0; wsz = 0; }
399 if( !wts ) wts = new float[wsz = binsz+1];
400 if( lutsz != lsz ) { delete lut; lut = 0; lsz = 0; }
401 if( !lut ) lut = new int[lsz = lutsz];
403 if(!engine) engine = new HistEqEngine(this,
404 get_project_smp() + 1, get_project_smp() + 1);
405 engine->process_packages(HistEqEngine::HISTEQ, frame);
407 // sum the results, not all clients may have run
408 memset(bins, 0, binsz*sizeof(bins[0]));
409 for( int i=0, n=engine->get_total_clients(); i<n; ++i ) {
410 HistEqUnit *unit = (HistEqUnit*)engine->get_client(i);
411 if( !unit->valid ) continue;
412 for( int i=0; i<binsz; ++i ) bins[i] += unit->bins[i];
415 // Remove top and bottom from calculations.
416 // Doesn't work in high precision colormodels.
417 int n = frame->get_w() * frame->get_h();
418 int binsz1 = binsz-1, lutsz1 = lutsz-1;
419 n -= bins[0]; bins[0] = 0;
420 n -= bins[binsz1]; bins[binsz1] = 0;
423 // integrate and normalize
424 for( int i=0,t=0; i<binsz; ++i ) { t += bins[i]; wts[i] = (float)t / n; }
426 // exclude margins (+-2%)
427 float fmn = 0.02f, fmx = 1. - fmn;
428 int mn = 0; while( mn < binsz && wts[mn] < fmn ) ++mn;
429 int mx = binsz; while( mx > mn && wts[mx-1] > fmx ) --mx;
430 n = mx-mn+1; fit(&wts[mn], n, a, b);
433 if( (a*n + b) < 1 ) a = (1 - b) / n;
434 if( b > 0 ) { a = (a*n + b) / n; b = 0; }
435 double blend = config.blend, blend1 = 1-blend;
436 double r = (double)binsz1 / lutsz1;
437 float g0 = config.gain, g1 = 1-g0;
438 for( int i=0; i<lutsz; ++i ) {
440 int is0 = is, is1 = is0+1;
441 double s0 = is-is0, s1 = 1-s0;
442 // piecewise linear interp btwn wts[is]..wts[is+1]
443 float u = wts[is0]*s0 + wts[is1]*s1;
446 // blend bins eq with linear regression, add scalar gain
447 float t = u*blend + v*blend1;
448 int iy = (t*lutsz1)*g0 + i*g1;
449 lut[i] = bclip(iy, 0, lutsz1);
451 if( config.plot && gui_open() )
452 send_render_gui(this);
454 engine->process_packages(HistEqEngine::APPLY, frame);
458 HistEqPackage::HistEqPackage()
463 HistEqUnit::HistEqUnit(HistEqEngine *server, HistEqMain *plugin)
466 this->plugin = plugin;
467 this->server = server;
473 HistEqUnit::~HistEqUnit()
478 void HistEqUnit::process_package(LoadPackage *package)
480 if( binsz != plugin->binsz ) {
481 delete bins; bins = 0; binsz = 0;
484 bins = new int[binsz = plugin->binsz];
488 bzero(bins, binsz*sizeof(bins[0]));
491 HistEqPackage *pkg = (HistEqPackage*)package;
492 switch( server->operation ) {
493 case HistEqEngine::HISTEQ: {
495 #define HISTEQ_HEAD(type) { \
496 type **rows = (type**)data->get_rows(); \
497 for( int iy=pkg->y0; iy<pkg->y1; ++iy ) { \
498 type *row = rows[iy]; \
499 for( int ix=0; ix<w; ++ix ) {
501 #define HISTEQ_TAIL(components) \
507 VFrame *data = server->data;
508 int colormodel = data->get_color_model();
509 int w = data->get_w(), comps = BC_CModels::components(colormodel);
511 switch( colormodel ) {
514 HISTEQ_HEAD(unsigned char)
515 int r = row[0], g = row[1], b = row[2];
520 case BC_RGBA16161616:
521 HISTEQ_HEAD(uint16_t)
522 int r = row[0], g = row[1], b = row[2];
529 int r = (int)(row[0] * 0x5555);
530 int g = (int)(row[1] * 0x5555);
531 int b = (int)(row[2] * 0x5555);
532 int i = r + g + b; bclamp(i, 0,0xffff);
537 HISTEQ_HEAD(unsigned char)
538 int y = (row[0]<<8) + row[0];
539 int u = (row[1]<<8) + row[1];
540 int v = (row[2]<<8) + row[2];
542 YUV::yuv.yuv_to_rgb_16(r, g, b, y, u, v);
547 case BC_YUVA16161616:
548 HISTEQ_HEAD(uint16_t)
550 YUV::yuv.yuv_to_rgb_16(r, g, b, row[0], row[1], row[2]);
556 case HistEqEngine::APPLY: {
557 #define PROCESS_RGB(type, components, max) { \
558 type **rows = (type**)input->get_rows(); \
559 for( int iy=pkg->y0; iy<pkg->y1; ++iy ) { \
560 type *row = rows[iy]; \
561 int x1 = !split ? w : (iy * w) / h; \
562 for( int x=0; x<x1; ++x ) { \
563 int r = row[0], g = row[1], b = row[2]; \
564 row[0] = lut[r]; row[1] = lut[g]; row[2] = lut[b]; \
569 #define PROCESS_YUV(type, components, max) { \
570 type **rows = (type**)input->get_rows(); \
571 for( int iy=pkg->y0; iy<pkg->y1; ++iy ) { \
572 type *row = rows[iy]; \
573 int x1 = !split ? w : (iy * w) / h; \
574 for( int ix=0; ix<x1; ++ix ) { \
575 int r, g, b, y, u, v; \
576 if( max == 0xff ) { \
577 y = (row[0] << 8) | row[0]; \
578 u = (row[1] << 8) | row[1]; \
579 v = (row[2] << 8) | row[2]; \
582 y = row[0]; u = row[1]; v = row[2]; \
584 YUV::yuv.yuv_to_rgb_16(r, g, b, y, u, v); \
585 YUV::yuv.rgb_to_yuv_16(lut[r], lut[g], lut[b], y, u, v); \
586 if( max == 0xff ) { \
592 row[0] = y; row[1] = u; row[2] = v; \
598 #define PROCESS_FLOAT(components) { \
599 float **rows = (float**)input->get_rows(); \
600 for( int iy=pkg->y0; iy<pkg->y1; ++iy ) { \
601 float *row = rows[iy]; \
602 int x1 = !split ? w : (iy * w) / h; \
603 for( int ix=0; ix<x1; ++ix ) { \
604 int r = row[0]*0xffff, g = row[1]*0xffff, b = row[2]*0xffff; \
605 bclamp(r, 0,0xffff); bclamp(g, 0,0xffff); bclamp(b, 0,0xffff); \
606 row[0] = (float)lut[r] / 0xffff; \
607 row[1] = (float)lut[g] / 0xffff; \
608 row[2] = (float)lut[b] / 0xffff; \
614 VFrame *input = plugin->input;
615 int w = input->get_w(), h = input->get_h();
616 int split = plugin->config.split;
617 int *lut = plugin->lut;
619 switch(input->get_color_model()) {
621 PROCESS_RGB(unsigned char, 3, 0xff)
624 PROCESS_RGB(unsigned char, 4, 0xff)
627 PROCESS_RGB(uint16_t, 3, 0xffff)
629 case BC_RGBA16161616:
630 PROCESS_RGB(uint16_t, 4, 0xffff)
639 PROCESS_YUV(unsigned char, 3, 0xff)
642 PROCESS_YUV(unsigned char, 4, 0xff)
645 PROCESS_YUV(uint16_t, 3, 0xffff)
647 case BC_YUVA16161616:
648 PROCESS_YUV(uint16_t, 4, 0xffff)
655 HistEqEngine::HistEqEngine(HistEqMain *plugin,
658 : LoadServer(total_clients, total_packages)
660 this->plugin = plugin;
663 void HistEqEngine::init_packages()
665 int h = data->get_h();
668 for( int i=0, n=get_total_packages(); i<n; ) {
669 HistEqPackage *pkg = (HistEqPackage *)get_package(i);
670 int y1 = ++i * h / n;
671 pkg->y0 = y0; pkg->y1 = y1;
674 for( int i=0, n=get_total_clients(); i<n; ++i ) {
675 HistEqUnit *unit = (HistEqUnit*)get_client(i);
676 unit->valid = 0; // set if unit runs
680 LoadClient* HistEqEngine::new_client()
682 return new HistEqUnit(this, plugin);
685 LoadPackage* HistEqEngine::new_package()
687 return new HistEqPackage;
690 void HistEqEngine::process_packages(int operation, VFrame *data)
693 this->operation = operation;
695 LoadServer::process_packages();