8477ed8296524d079d407ea65f1925a50971d487
[goodguy/cinelerra.git] / cinelerra-5.1 / plugins / histeq / histeq.C
1
2 /*
3  * CINELERRA
4  * Copyright (C) 2008-2012 Adam Williams <broadcast at earthling dot net>
5  *
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.
10  *
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.
15  *
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
19  *
20  */
21
22 #include <math.h>
23 #include <stdint.h>
24 #include <string.h>
25 #include <unistd.h>
26
27 #include "histeq.h"
28 #include "filexml.h"
29 #include "language.h"
30 #include "loadbalance.h"
31 #include "bccolors.h"
32 #include "vframe.h"
33
34 class HistEqMain;
35 class HistEqEngine;
36 class HistEqWindow;
37
38 REGISTER_PLUGIN(HistEqMain)
39
40 HistEqConfig::HistEqConfig()
41 {
42         split = 0;
43         plot = 0;
44         blend = 0.5;
45         gain = 1.0;
46 }
47
48 HistEqConfig::~HistEqConfig()
49 {
50 }
51
52 void HistEqConfig::copy_from(HistEqConfig &that)
53 {
54         split = that.split;
55         plot = that.plot;
56         blend = that.blend;
57         gain = that.gain;
58 }
59
60 int HistEqConfig::equivalent(HistEqConfig &that)
61 {
62         return 1;
63 }
64
65 void HistEqConfig::interpolate(HistEqConfig &prev, HistEqConfig &next,
66                 int64_t prev_frame, int64_t next_frame, int64_t current_frame)
67 {
68         copy_from(prev);
69 }
70
71
72 HistEqWindow::HistEqWindow(HistEqMain *plugin)
73  : PluginClientWindow(plugin, plugin->w, plugin->h, plugin->w, plugin->h, 0)
74 {
75         this->plugin = plugin;
76 }
77
78 HistEqWindow::~HistEqWindow()
79 {
80 }
81
82
83 void HistEqWindow::create_objects()
84 {
85         int xs10 = xS(10), xs60 = xS(60);
86         int ys10 = yS(10);
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;
91         
92         add_subwindow(split = new HistEqSplit(this, plugin, x, y));
93         y += split->get_h() + ys10;
94
95         add_subwindow(plot = new HistEqPlot(this, plugin, x, y));
96         y += plot->get_h() + ys10;
97
98         int x1 = x + xs60;
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;
102
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;
106
107         show_window();
108 }
109
110 void HistEqWindow::update()
111 {
112 }
113
114 HistEqCanvas::HistEqCanvas(HistEqWindow *gui, HistEqMain *plugin,
115                 int x, int y, int w, int h)
116  : BC_SubWindow(x, y, w, h, BLACK)
117 {
118         this->gui = gui;
119         this->plugin = plugin;
120 }
121 void HistEqCanvas::clear()
122 {
123         clear_box(0,0, get_w(),get_h());
124 }
125
126 void HistEqCanvas::draw_bins(HistEqMain *plugin)
127 {
128         set_color(GREEN);
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();
135         int x0 = 0, x = 0;
136         while( x < cw ) {
137                 int mx = data[x0];
138                 int x1 = (n * ++x) / cw;
139                 for( int i=x0; ++i<x1; ) {
140                         int m = data[i];
141                         if( m > mx ) mx = m;
142                 }
143                 double lmx = mx>0 ? log(mx) : 0;
144                 int y1 = ch * (1 - lmx/lmax);
145                 draw_line(x,0, x,y1);
146                 x0 = x1;
147         }
148 }
149
150 void HistEqCanvas::draw_wts(HistEqMain *plugin)
151 {
152         float *wts = plugin->wts;
153         int n = plugin->binsz;
154         set_color(BLUE);
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;
158         while( x1 < cw1 ) {
159                 int x0 = x1++, y0 = y1;
160                 int is = (n * x1) / cw1;
161                 float fy = wts[is]*g0 + ((float)x1/cw1)*g1;
162                 y1 = (1-fy) * ch1;
163                 draw_line(x0,y0, x1,y1);
164         }
165 }
166
167 void HistEqCanvas::draw_reg(HistEqMain *plugin)
168 {
169         set_color(WHITE);
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);
179 }
180
181 void HistEqCanvas::draw_lut(HistEqMain *plugin)
182 {
183         int *lut = plugin->lut;
184         int n = plugin->lutsz-1;
185         double s = 1. / n;
186         set_color(RED);
187         int cw1 = get_w()-1, ch1 = get_h()-1;
188         int x1 = 0, y1 = ch1;
189         while( x1 < cw1 ) {
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);
194         }
195 }
196
197 void HistEqCanvas::update(HistEqMain *plugin)
198 {
199         clear();
200         draw_bins(plugin);
201         draw_wts(plugin);
202         draw_reg(plugin);
203         draw_lut(plugin);
204         flash();
205 }
206
207 HistEqSplit::HistEqSplit(HistEqWindow *gui, HistEqMain *plugin, int x, int y)
208  : BC_CheckBox(x, y, plugin->config.split, _("Split output"))
209 {
210         this->gui = gui;
211         this->plugin = plugin;
212 }
213 HistEqSplit::~HistEqSplit()
214 {
215 }
216
217 int HistEqSplit::handle_event()
218 {
219         plugin->config.split = get_value();
220         plugin->send_configure_change();
221         return 1;
222 }
223
224 HistEqPlot::HistEqPlot(HistEqWindow *gui, HistEqMain *plugin, int x, int y)
225  : BC_CheckBox(x, y, plugin->config.plot, _("Plot bins/lut"))
226 {
227         this->gui = gui;
228         this->plugin = plugin;
229 }
230 HistEqPlot::~HistEqPlot()
231 {
232 }
233
234 int HistEqPlot::handle_event()
235 {
236         plugin->config.plot = get_value();
237         plugin->send_configure_change();
238         return 1;
239 }
240
241 HistEqBlend::HistEqBlend(HistEqWindow *gui, HistEqMain *plugin, int x, int y)
242  : BC_FSlider(x, y, 0, xS(150), yS(200), 0, xS(1.0), plugin->config.blend, 0)
243 {
244         this->gui = gui;
245         this->plugin = plugin;
246         set_precision(0.01);
247 }
248 HistEqBlend::~HistEqBlend()
249 {
250 }
251
252 int HistEqBlend::handle_event()
253 {
254         plugin->config.blend = get_value();
255         plugin->send_configure_change();
256         return 1;
257 }
258
259
260 HistEqGain::HistEqGain(HistEqWindow *gui, HistEqMain *plugin, int x, int y)
261  : BC_FSlider(x, y, 0, xS(150), yS(200), 0, xS(1.0), plugin->config.gain, 0)
262 {
263         this->gui = gui;
264         this->plugin = plugin;
265         set_precision(0.01);
266 }
267 HistEqGain::~HistEqGain()
268 {
269 }
270
271 int HistEqGain::handle_event()
272 {
273         plugin->config.gain = get_value();
274         plugin->send_configure_change();
275         return 1;
276 }
277
278
279 HistEqMain::HistEqMain(PluginServer *server)
280  : PluginVClient(server)
281 {
282         w = xS(300);  h = yS(375);
283         engine = 0;
284         sz = 0;
285         binsz = bsz = 0;  bins = 0;
286         lutsz = lsz = 0;  lut = 0;
287         wsz = 0;          wts = 0;
288         a = 0;  b = 0;
289 }
290
291 HistEqMain::~HistEqMain()
292 {
293         delete engine;
294         delete [] bins;
295         delete [] lut;
296         delete [] wts;
297 }
298
299 const char* HistEqMain::plugin_title() { return N_("HistEq"); }
300 int HistEqMain::is_realtime() { return 1; }
301
302
303 NEW_WINDOW_MACRO(HistEqMain, HistEqWindow)
304
305 LOAD_CONFIGURATION_MACRO(HistEqMain, HistEqConfig)
306
307 void HistEqMain::update_gui()
308 {
309         if( !thread ) return;
310         if( !load_configuration() ) return;
311         ((HistEqWindow*)thread->window)->lock_window("HistEqMain::update_gui");
312         HistEqWindow* gui = (HistEqWindow*)thread->window;
313         gui->update();
314         gui->unlock_window();
315 }
316
317 void HistEqMain::save_data(KeyFrame *keyframe)
318 {
319         FileXML output;
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);
328         output.append_tag();
329         output.tag.set_title("/HISTEQ");
330         output.append_tag();
331         output.append_newline();
332         output.terminate_string();
333 }
334
335 void HistEqMain::read_data(KeyFrame *keyframe)
336 {
337         FileXML input;
338         input.set_shared_input(keyframe->xbuf);
339         int result = 0;
340         while( !(result=input.read_tag()) ) {
341                 if( input.tag.title_is("HISTEQ") ) {
342                         if(is_defaults()) {
343                                 w = input.tag.get_property("W", w);
344                                 h = input.tag.get_property("H", h);
345                         }
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);
350                 }
351         }
352 }
353
354 void HistEqMain::render_gui(void *data)
355 {
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();
361 }
362
363 // regression line
364 static void fit(float *dat, int n, double &a, double &b)
365 {
366         double sy = 0;
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];
370         double m = (n+1)/2;
371         double ss = 2. * ((m+1)*m*(m-1)/3. + m*(m-1)/2.);
372         a = s / ss;
373         b = my - mx*a;
374 }
375
376 int HistEqMain::process_buffer(VFrame *frame, int64_t start_position, double frame_rate)
377 {
378         load_configuration();
379         read_frame(frame, 0, start_position, frame_rate, 0);
380         this->input = this->output = frame;
381
382         int colormodel = frame->get_color_model();
383         lutsz = 0x10000;
384         binsz = (3*0xffff) + 1;
385         switch( colormodel ) {
386         case BC_RGB888:
387         case BC_RGBA8888:
388                 binsz = (3*0xff) + 1;
389                 lutsz = 0x100;
390                 break;
391         case BC_RGB_FLOAT:
392         case BC_RGBA_FLOAT:
393                 binsz = (3*0x5555) + 1;
394                 break;
395         }
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];
402
403         if(!engine) engine = new HistEqEngine(this,
404                 get_project_smp() + 1, get_project_smp() + 1);
405         engine->process_packages(HistEqEngine::HISTEQ, frame);
406
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];
413         }
414
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;
421         sz = n;
422
423 // integrate and normalize
424         for( int i=0,t=0; i<binsz; ++i ) { t += bins[i];  wts[i] = (float)t / n; }
425         wts[binsz] = 1.f;
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);
431 // home y intercept
432         b -= a * mn;
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 ) {
439                 double is = i * r;
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;
444 // regression line
445                 float v = is*a + b;
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);
450         }
451         if( config.plot && gui_open() )
452                 send_render_gui(this);
453
454         engine->process_packages(HistEqEngine::APPLY, frame);
455         return 0;
456 }
457
458 HistEqPackage::HistEqPackage()
459  : LoadPackage()
460 {
461 }
462
463 HistEqUnit::HistEqUnit(HistEqEngine *server, HistEqMain *plugin)
464  : LoadClient(server)
465 {
466         this->plugin = plugin;
467         this->server = server;
468         bins = 0;
469         binsz = 0;
470         valid = 0;
471 }
472
473 HistEqUnit::~HistEqUnit()
474 {
475         delete [] bins;
476 }
477
478 void HistEqUnit::process_package(LoadPackage *package)
479 {
480         if( binsz != plugin->binsz ) {
481                 delete bins;  bins = 0;  binsz = 0;
482         }
483         if( !bins ) {
484                 bins = new int[binsz = plugin->binsz];
485         }
486         if( !valid ) {
487                 valid = 1;
488                 bzero(bins, binsz*sizeof(bins[0]));
489         }
490
491         HistEqPackage *pkg = (HistEqPackage*)package;
492         switch( server->operation ) {
493         case HistEqEngine::HISTEQ: {
494
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 ) {
500
501 #define HISTEQ_TAIL(components) \
502                         ++bins[i]; \
503                         row += components; \
504                 } \
505         } \
506 }
507                 VFrame *data = server->data;
508                 int colormodel = data->get_color_model();
509                 int w = data->get_w(), comps = BC_CModels::components(colormodel);
510
511                 switch( colormodel ) {
512                 case BC_RGB888:
513                 case BC_RGBA8888:
514                         HISTEQ_HEAD(unsigned char)
515                         int r = row[0], g = row[1], b = row[2];
516                         int i = r + g + b;
517                         HISTEQ_TAIL(comps)
518                         break;
519                 case BC_RGB161616:
520                 case BC_RGBA16161616:
521                         HISTEQ_HEAD(uint16_t)
522                         int r = row[0], g = row[1], b = row[2];
523                         int i = r + g + b;
524                         HISTEQ_TAIL(comps)
525                         break;
526                 case BC_RGB_FLOAT:
527                 case BC_RGBA_FLOAT:
528                         HISTEQ_HEAD(float)
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);
533                         HISTEQ_TAIL(comps)
534                         break;
535                 case BC_YUV888:
536                 case BC_YUVA8888:
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];
541                         int r, g, b;
542                         YUV::yuv.yuv_to_rgb_16(r, g, b, y, u, v);
543                         int i = r + g + b;
544                         HISTEQ_TAIL(comps)
545                         break;
546                 case BC_YUV161616:
547                 case BC_YUVA16161616:
548                         HISTEQ_HEAD(uint16_t)
549                         int r, g, b;
550                         YUV::yuv.yuv_to_rgb_16(r, g, b, row[0], row[1], row[2]);
551                         int i = r + g + b;
552                         HISTEQ_TAIL(comps)
553                         break;
554                 }
555                 break; }
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]; \
565                         row += components; \
566                 } \
567         } \
568 }
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]; \
580                         } \
581                         else { \
582                                 y = row[0]; u = row[1]; v = row[2]; \
583                         } \
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 ) { \
587                                 row[0] = y >> 8; \
588                                 row[1] = u >> 8; \
589                                 row[2] = v >> 8; \
590                         } \
591                         else { \
592                                 row[0] = y; row[1] = u; row[2] = v; \
593                         } \
594                         row += components; \
595                 } \
596         } \
597 }
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; \
609                         row += components; \
610                 } \
611         } \
612 }
613
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;
618
619                 switch(input->get_color_model()) {
620                 case BC_RGB888:
621                         PROCESS_RGB(unsigned char, 3, 0xff)
622                         break;
623                 case BC_RGBA8888:
624                         PROCESS_RGB(unsigned char, 4, 0xff)
625                         break;
626                 case BC_RGB161616:
627                         PROCESS_RGB(uint16_t, 3, 0xffff)
628                         break;
629                 case BC_RGBA16161616:
630                         PROCESS_RGB(uint16_t, 4, 0xffff)
631                         break;
632                 case BC_RGB_FLOAT:
633                         PROCESS_FLOAT(3);
634                         break;
635                 case BC_RGBA_FLOAT:
636                         PROCESS_FLOAT(4);
637                         break;
638                 case BC_YUV888:
639                         PROCESS_YUV(unsigned char, 3, 0xff)
640                         break;
641                 case BC_YUVA8888:
642                         PROCESS_YUV(unsigned char, 4, 0xff)
643                         break;
644                 case BC_YUV161616:
645                         PROCESS_YUV(uint16_t, 3, 0xffff)
646                         break;
647                 case BC_YUVA16161616:
648                         PROCESS_YUV(uint16_t, 4, 0xffff)
649                         break;
650                 }
651                 break; }
652         }
653 }
654
655 HistEqEngine::HistEqEngine(HistEqMain *plugin,
656         int total_clients,
657         int total_packages)
658  : LoadServer(total_clients, total_packages)
659 {
660         this->plugin = plugin;
661 }
662
663 void HistEqEngine::init_packages()
664 {
665         int h = data->get_h();
666         int y0 = 0;
667
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;
672                 y0 = y1;
673         }
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
677         }
678 }
679
680 LoadClient* HistEqEngine::new_client()
681 {
682         return new HistEqUnit(this, plugin);
683 }
684
685 LoadPackage* HistEqEngine::new_package()
686 {
687         return new HistEqPackage;
688 }
689
690 void HistEqEngine::process_packages(int operation, VFrame *data)
691 {
692         this->data = data;
693         this->operation = operation;
694
695         LoadServer::process_packages();
696 }
697
698