add descratch plugin, single frame silence insert, docs
[goodguy/history.git] / cinelerra-5.1 / plugins / descratch / descratch.C
1 /*
2 DeScratch - Scratches Removing Filter
3 Plugin for Avisynth 2.5
4 Copyright (c)2003-2016 Alexander G. Balakhnin aka Fizick
5 bag@hotmail.ru  http://avisynth.org.ru
6
7 This program is FREE software under GPL licence v2.
8
9 This plugin removes vertical scratches from digitized films.
10 Reworked for cin5 by GG. 03/2018, from the laws of Fizick's
11 */
12
13
14 #include "clip.h"
15 #include "filexml.h"
16 #include "language.h"
17 #include "descratch.h"
18
19 REGISTER_PLUGIN(DeScratchMain)
20
21 DeScratchMain::DeScratchMain(PluginServer *server)
22  : PluginVClient(server)
23 {
24         inf = 0;  sz_inf = 0;
25         src = 0;  dst = 0;
26         tmp_frame = 0;
27         blurry = 0;
28         overlay_frame = 0;
29 }
30
31 DeScratchMain::~DeScratchMain()
32 {
33         delete [] inf;
34         delete src;
35         delete dst;
36         delete blurry;
37         delete tmp_frame;
38         delete overlay_frame;
39 }
40
41 const char* DeScratchMain::plugin_title() { return N_("DeScratch"); }
42 int DeScratchMain::is_realtime() { return 1; }
43
44 void DeScratchConfig::reset()
45 {
46         threshold = 24;
47         asymmetry = 16;
48         min_width = 1;
49         max_width = 3;
50         min_len = 1;
51         max_len = 100;
52         max_angle = 45;
53         blur_len = 4;
54         gap_len = 10;
55         mode_y = MODE_ALL;
56         mode_u = MODE_NONE;
57         mode_v = MODE_NONE;
58         mark = 0;
59         ffade = 100;
60         border = 2;
61 }
62
63 DeScratchConfig::DeScratchConfig()
64 {
65         reset();
66 }
67
68 DeScratchConfig::~DeScratchConfig()
69 {
70 }
71
72 int DeScratchConfig::equivalent(DeScratchConfig &that)
73 {
74         return threshold == that.threshold &&
75                 asymmetry == that.asymmetry &&
76                 min_width == that.min_width &&
77                 max_width == that.max_width &&
78                 min_len == that.min_len &&
79                 max_len == that.max_len &&
80                 max_angle == that.max_angle &&
81                 blur_len == that.blur_len &&
82                 gap_len == that.gap_len &&
83                 mode_y == that.mode_y &&
84                 mode_u == that.mode_u &&
85                 mode_v == that.mode_v &&
86                 mark == that.mark &&
87                 ffade == that.ffade &&
88                 border == that.border;
89 }
90 void DeScratchConfig::copy_from(DeScratchConfig &that)
91 {
92         threshold = that.threshold;
93         asymmetry = that.asymmetry;
94         min_width = that.min_width;
95         max_width = that.max_width;
96         min_len = that.min_len;
97         max_len = that.max_len;
98         max_angle = that.max_angle;
99         blur_len = that.blur_len;
100         gap_len = that.gap_len;
101         mode_y = that.mode_y;
102         mode_u = that.mode_u;
103         mode_v = that.mode_v;
104         mark = that.mark;
105         ffade = that.ffade;
106         border = that.border;
107 }
108
109 void DeScratchConfig::interpolate(DeScratchConfig &prev, DeScratchConfig &next,
110                 int64_t prev_frame, int64_t next_frame, int64_t current_frame)
111 {
112         copy_from(prev);
113 }
114
115 LOAD_CONFIGURATION_MACRO(DeScratchMain, DeScratchConfig)
116
117 void DeScratchMain::save_data(KeyFrame *keyframe)
118 {
119         FileXML output;
120 // cause data to be stored directly in text
121         output.set_shared_output(keyframe->get_data(), MESSAGESIZE);
122 // Store data
123         output.tag.set_title("DESCRATCH");
124         output.tag.set_property("THRESHOLD", config.threshold);
125         output.tag.set_property("ASYMMETRY", config.asymmetry);
126         output.tag.set_property("MIN_WIDTH", config.min_width);
127         output.tag.set_property("MAX_WIDTH", config.max_width);
128         output.tag.set_property("MIN_LEN", config.min_len);
129         output.tag.set_property("MAX_LEN", config.max_len);
130         output.tag.set_property("MAX_ANGLE", config.max_angle);
131         output.tag.set_property("BLUR_LEN", config.blur_len);
132         output.tag.set_property("GAP_LEN", config.gap_len);
133         output.tag.set_property("MODE_Y", config.mode_y);
134         output.tag.set_property("MODE_U", config.mode_u);
135         output.tag.set_property("MODE_V", config.mode_v);
136         output.tag.set_property("MARK", config.mark);
137         output.tag.set_property("FFADE", config.ffade);
138         output.tag.set_property("BORDER", config.border);
139         output.append_tag();
140         output.tag.set_title("/DESCRATCH");
141         output.append_tag();
142         output.append_newline();
143         output.terminate_string();
144 }
145
146 void DeScratchMain::read_data(KeyFrame *keyframe)
147 {
148         FileXML input;
149         input.set_shared_input(keyframe->get_data(), strlen(keyframe->get_data()));
150
151         int result = 0;
152
153         while( !(result = input.read_tag()) ) {
154                 if(input.tag.title_is("DESCRATCH")) {
155                         config.threshold = input.tag.get_property("THRESHOLD", config.threshold);
156                         config.asymmetry         = input.tag.get_property("ASYMMETRY", config.asymmetry);
157                         config.min_width = input.tag.get_property("MIN_WIDTH", config.min_width);
158                         config.max_width = input.tag.get_property("MAX_WIDTH", config.max_width);
159                         config.min_len   = input.tag.get_property("MIN_LEN", config.min_len);
160                         config.max_len   = input.tag.get_property("MAX_LEN", config.max_len);
161                         config.max_angle = input.tag.get_property("MAX_ANGLE", config.max_angle);
162                         config.blur_len  = input.tag.get_property("BLUR_LEN", config.blur_len);
163                         config.gap_len   = input.tag.get_property("GAP_LEN", config.gap_len);
164                         config.mode_y    = input.tag.get_property("MODE_Y", config.mode_y);
165                         config.mode_u    = input.tag.get_property("MODE_U", config.mode_u);
166                         config.mode_v    = input.tag.get_property("MODE_V", config.mode_v);
167                         config.mark      = input.tag.get_property("MARK", config.mark);
168                         config.ffade     = input.tag.get_property("FFADE", config.ffade);
169                         config.border    = input.tag.get_property("BORDER", config.border);
170                 }
171         }
172 }
173
174 void DeScratchMain::get_extrems_plane(int comp, int thresh)
175 {
176         uint8_t **rows = blurry->get_rows();
177         int d = config.max_width, d1 = d+1, wd = src_w - d1;
178         int bpp = 3, dsz = d * bpp;
179         int asym = config.asymmetry;
180         if( thresh > 0 ) {      // black (low value) scratches
181                 for( int y=0; y<src_h; ++y ) {
182                         uint8_t *ip = inf + y*src_w;
183                         int x = 0;
184                         for( ; x<d1; ++x ) *ip++ = SD_NULL;
185                         uint8_t *dp = rows[y] + x*bpp + comp;
186                         for( ; x<wd; ++x,dp+=bpp ) {
187                                 uint8_t *lp = dp-dsz, *rp = dp+dsz;
188                                 *ip++ = (lp[0]-*dp) > thresh && (rp[0]-*dp) > thresh &&
189                                         (abs(lp[-bpp]-rp[+bpp]) <= asym) &&
190                                          ((lp[0]-lp[+bpp]) + (rp[0]-rp[-bpp]) >
191                                           (lp[-bpp]-lp[0]) + (rp[bpp]-rp[0])) ?
192                                                 SD_EXTREM : SD_NULL; // sharp extremum found
193                         }
194                         for( ; x<src_w; ++x ) *ip++ = SD_NULL;
195                 }
196         }
197         else {                  // white (high value) scratches
198                 for( int y=0; y<src_h; ++y ) {
199                         uint8_t *ip = inf + y*src_w;
200                         int x = 0;
201                         for( ; x<d1; ++x ) *ip++ = SD_NULL;
202                         uint8_t *dp = rows[y] + x*bpp + comp;
203                         for( ; x<wd; ++x,dp+=bpp ) {
204                                 uint8_t *lp = dp-dsz, *rp = dp+dsz;
205                                 *ip++ = (lp[0]-*dp) < thresh && (rp[0]-*dp) < thresh &&
206                                         (abs(lp[-bpp]-rp[+bpp]) <= asym) &&
207                                          ((lp[0]-lp[+bpp]) + (rp[0]-rp[-bpp]) <
208                                           (lp[-bpp]-lp[0]) + (rp[bpp]-rp[0])) ?
209                                                 SD_EXTREM : SD_NULL; // sharp extremum found
210                         }
211                         for( ; x<src_w; ++x ) *ip++ = SD_NULL;
212                 }
213         }
214 }
215
216 //
217 void DeScratchMain::remove_min_extrems_plane(int comp, int thresh)
218 {
219         uint8_t **rows = blurry->get_rows();
220         int d = config.min_width, d1 = d+1, wd = src_w - d1;
221         int bpp = 3, dsz = d * bpp;
222         int asym = config.asymmetry;
223         if( thresh > 0 ) {      // black (low value) scratches
224                 for( int y=0; y<src_h; ++y ) {
225                         uint8_t *ip = inf + y*src_w;
226                         uint8_t *dp = rows[y] + d1*bpp + comp;
227                         for( int x=d1; x<wd; ++x,++ip,dp+=bpp ) {
228                                 if( *ip != SD_EXTREM ) continue;
229                                 uint8_t *lp = dp-dsz, *rp = dp+dsz;
230                                 if( (lp[0]-*dp) > thresh && (rp[0]-*dp) > thresh &&
231                                         (abs(lp[-bpp]-rp[+bpp]) <= asym) &&
232                                          ((lp[0]-lp[+bpp]) + (rp[0]-rp[-bpp]) >
233                                           (lp[-bpp]-lp[0]) + (rp[bpp]-rp[0])) )
234                                                 *ip = SD_NULL; // sharp extremum found
235                         }
236                 }
237         }
238         else {                  // white (high value) scratches
239                 for( int y=0; y<src_h; ++y ) {
240                         uint8_t *ip = inf + y*src_w;
241                         uint8_t *dp = rows[y] + d1*bpp + comp;
242                         for( int x=d1; x<wd; ++x,++ip,dp+=bpp ) {
243                                 if( *ip != SD_EXTREM ) continue;
244                                 uint8_t *lp = dp-dsz, *rp = dp+dsz;
245                                 if( (lp[0]-*dp) < thresh && (rp[0]-*dp) < thresh &&
246                                         (abs(lp[-bpp]-rp[+bpp]) <= asym) &&
247                                          ((lp[0]-lp[+bpp]) + (rp[0]-rp[-bpp]) <
248                                           (lp[-bpp]-lp[0]) + (rp[bpp]-rp[0])) )
249                                                 *ip = SD_NULL; // sharp extremum found
250                         }
251                 }
252         }
253 }
254
255 void DeScratchMain::close_gaps()
256 {
257         int len = config.gap_len * src_h / 100;
258         for( int y=len; y<src_h; ++y ) {
259                 uint8_t *ip = inf + y*src_w;
260                 for( int x=0; x<src_w; ++x,++ip ) {
261                         if( *ip != SD_EXTREM ) continue;
262                         uint8_t *bp = ip;                       // expand to previous lines in range
263                         for( int i=len; --i>0; ) *(bp-=src_w) = SD_EXTREM;
264                 }
265         }
266 }
267
268 void DeScratchMain::test_scratches()
269 {
270         int w2 = src_w - 2;
271         int min_len = config.min_len * src_h / 100;
272         int max_len = config.max_len * src_h / 100;
273         int maxwidth = config.max_width*2 + 1;
274         int maxangle = config.max_angle;
275
276         for( int y=0; y<src_h; ++y ) {
277                 for( int x=2; x<w2; ++x ) {
278                         int ofs = y*src_w + x;                  // offset of first candidate
279                         if( inf[ofs] != SD_EXTREM ) continue;
280                         int ctr = ofs+1, nctr = ctr;            // centered to inf for maxwidth=3
281                         int hy = src_h - y, len;
282                         for( len=0; len<hy; ++len ) {           // cycle along inf
283                                 uint8_t *ip = inf + ctr;
284                                 int n = 0;                      // number good points in row
285                                 if( maxwidth >= 3 ) {
286                                         if( ip[-2] == SD_EXTREM ) { ip[-2] = SD_TESTED; nctr = ctr-2; ++n; }
287                                         if( ip[+2] == SD_EXTREM ) { ip[+2] = SD_TESTED; nctr = ctr+2; ++n; }
288                                 }
289                                 if( ip[-1] == SD_EXTREM ) { ip[-1] = SD_TESTED; nctr = ctr-1; ++n; }
290                                 if( ip[+1] == SD_EXTREM ) { ip[+1] = SD_TESTED; nctr = ctr+1; ++n; }
291                                 if( ip[+0] == SD_EXTREM ) { ip[+0] = SD_TESTED; nctr = ctr+0; ++n; }
292                                 // end of points tests, check result for row:
293                                 // check gap and angle, if no points or big angle, it is end of inf
294                                 if( !n || abs(nctr%src_w - x) >= maxwidth+len*maxangle/57 ) break;
295                                 ctr = nctr + src_w;              // new center for next row test
296                         }
297                         int mask = len >= min_len && len <= max_len ? SD_GOOD : SD_REJECT;
298                         ctr = ofs+1; nctr = ctr;                // pass2
299                         for( len=0; len<hy; ++len ) {           // cycle along inf
300                                 uint8_t *ip = inf + ctr;
301                                 int n = 0;                      // number good points in row
302                                 if( maxwidth >= 3 ) {
303                                         if( ip[-2] == SD_TESTED ) { ip[-2] = mask; nctr = ctr-2; ++n; }
304                                         if( ip[+2] == SD_TESTED ) { ip[+2] = mask; nctr = ctr+2; ++n; }
305                                 }
306                                 if( ip[-1] == SD_TESTED ) { ip[-1] = mask; nctr = ctr-1; ++n; }
307                                 if( ip[+1] == SD_TESTED ) { ip[+1] = mask; nctr = ctr+1; ++n; }
308                                 if( ip[+0] == SD_TESTED ) { ip[+0] = mask; nctr = ctr+0; ++n; }
309                                 // end of points tests, check result for row:
310                                 // check gap and angle, if no points or big angle, it is end of inf
311                                 if( !n || abs(nctr%src_w - x) >= maxwidth+len*maxangle/57 ) break;
312                                 ctr = nctr + src_w;              // new center for next row test
313                         }
314                 }
315         }
316 }
317
318 void DeScratchMain::mark_scratches_plane(int comp, int mask, int value)
319 {
320         int bpp = 3, dst_w = dst->get_w(), dst_h = dst->get_h();
321         uint8_t **rows = dst->get_rows();
322         for( int y=0; y<dst_h; ++y ) {
323                 uint8_t *dp = rows[y] + comp;
324                 uint8_t *ip = inf + y*src_w;
325                 for( int x=0; x<dst_w; ++x,++ip,dp+=bpp ) {
326                         if( *ip == mask ) *dp = value;
327                 }
328         }
329 }
330
331 void DeScratchMain::remove_scratches_plane(int comp)
332 {
333         int r = config.max_width;
334         int fade = (config.ffade * 1024) / 100; // norm 2^10
335         int fade1 = 1024 - fade;
336         uint8_t *ip = inf;
337         uint8_t **src_rows = src->get_rows();
338         uint8_t **dst_rows = dst->get_rows();
339         uint8_t **blur_rows = blurry->get_rows();
340         int bpp = 3, margin = r+config.border+2, wm = src_w-margin;
341         float nrm = 1. / 1024.f, nrm2r = nrm / (2*r*bpp);
342
343         for( int y=0; y<src_h; ++y ) {
344                 int left = 0;
345                 uint8_t *inp = src_rows[y] + comp;
346                 uint8_t *out = dst_rows[y] + comp;
347                 uint8_t *blur = blur_rows[y] + comp;
348                 for( int x=margin; x<wm; ++x ) {
349                         uint8_t *dp = ip + x;
350                         if( (dp[+0]&SD_GOOD) && !(dp[-1]&SD_GOOD) ) left = x;
351                         if( left!=0 && (dp[+0]&SD_GOOD) && !(dp[+1]&SD_GOOD) ) { // the inf, left/right
352                                 int right = x;
353                                 int ctr = (left + right) / 2;                   // the inf center
354                                 int ls = ctr - r, rs = ctr + r;
355                                 int lt = ls - config.border - 1, rt = rs + config.border + 1;
356                                 lt *= bpp;  ls *= bpp;  rs *= bpp;  rt *= bpp;  // component index
357                                 for( int i=ls; i<=rs; i+=bpp ) {                // across the inf
358                                         int lv = inp[i] + blur[lt] - blur[i];
359                                         int rv = inp[i] + blur[rt] - blur[i];
360                                         lv = fade*lv + fade1*inp[lt];
361                                         rv = fade*rv + fade1*inp[rt];
362                                         int v = nrm2r*(lv*(rs-i) + rv*(i-ls));
363                                         out[i] = CLIP(v,0,255);
364                                 }
365                                 for( int i=lt; i<ls; i+=bpp ) {                  // at left border
366                                         int lv = inp[i] + blur[lt] - blur[i];
367                                         int v = nrm*(fade*lv + fade1*inp[lt]);
368                                         out[i] = CLIP(v,0,255);
369                                 }
370                                 for( int i=rt; i>rs; i-=bpp ) {                 // at right border
371                                         int rv = inp[i] + blur[rt] - blur[i];
372                                         int v = nrm*(fade*rv + fade1*inp[rt]);
373                                         out[i] = CLIP(v,0,255);
374                                 }
375                                 left = 0;
376                         }
377                 }
378                 ip += src_w;
379         }
380 }
381
382 void DeScratchMain::pass(int comp, int thresh)
383 {
384 // pass for current plane and current sign
385         get_extrems_plane(comp, thresh);
386         if( config.min_width > 1 )
387                 remove_min_extrems_plane(comp, thresh);
388         close_gaps();
389         test_scratches();
390         if( config.mark ) {
391                 int value = config.threshold > 0 ? 0 : 255;
392                 mark_scratches_plane(comp, SD_GOOD, value);
393                 mark_scratches_plane(comp, SD_REJECT, 127);
394         }
395         else
396                 remove_scratches_plane(comp);
397 }
398
399 void DeScratchMain::blur(int scale)
400 {
401         int tw = src_w, th = (src_h / scale) & ~1;
402         if( tmp_frame &&
403             (tmp_frame->get_w() != tw || tmp_frame->get_h() != th) ) {
404                 delete tmp_frame;  tmp_frame = 0;
405         }
406         if( !tmp_frame )
407                 tmp_frame = new VFrame(tw, th, BC_YUV888);
408
409         if( blurry &&
410             (blurry->get_w() != src_w || blurry->get_h() != src_h) ) {
411                 delete blurry;  blurry = 0;
412         }
413         if( !blurry )
414                 blurry = new VFrame(src_w, src_h, BC_YUV888);
415
416         overlay_frame->overlay(tmp_frame, src,
417                 0,0,src_w,src_h, 0,0,tw,th, 1.f, TRANSFER_NORMAL, LINEAR_LINEAR);
418         overlay_frame->overlay(blurry, tmp_frame,
419                 0,0,tw,th, 0,0,src_w,src_h, 1.f, TRANSFER_NORMAL, CUBIC_CUBIC);
420 }
421
422 void DeScratchMain::copy(int comp)
423 {
424         uint8_t **src_rows = src->get_rows();
425         uint8_t **dst_rows = dst->get_rows();
426         for( int y=0; y<src_h; ++y ) {
427                 uint8_t *sp = src_rows[y] + comp, *dp = dst_rows[y] + comp;
428                 for( int x=0; x<src_w; ++x,sp+=3,dp+=3 ) *sp = *dp;
429         }
430 }
431
432 void DeScratchMain::plane_pass(int comp, int mode)
433 {
434         int threshold = config.threshold;
435         if( comp != 0 ) threshold /= 2;  // fakey UV scaling
436         switch( mode ) {
437         case MODE_ALL:
438                 pass(comp, threshold);
439                 copy(comp);
440         case MODE_HIGH:         // fall thru
441                 threshold = -threshold;
442         case MODE_LOW:          // fall thru
443                 pass(comp, threshold);
444                 break;
445         }
446 }
447
448 int DeScratchMain::process_realtime(VFrame *input, VFrame *output)
449 {
450         load_configuration();
451         src_w = input->get_w();
452         src_h = input->get_h();
453         if( src_w >= 2*config.max_width+3 ) {
454                 if( !overlay_frame ) {
455                         int cpus = PluginClient::smp + 1;
456                         if( cpus > 8 ) cpus = 8;
457                         overlay_frame = new OverlayFrame(cpus);
458                 }
459                 if( src && (src->get_w() != src_w || src->get_h() != src_h) ) {
460                         delete src;  src = 0;
461                 }
462                 if( !src ) src = new VFrame(src_w, src_h, BC_YUV888);
463                 src->transfer_from(input);
464                 if( dst && (dst->get_w() != src_w || dst->get_h() != src_h) ) {
465                         delete dst;  dst = 0;
466                 }
467                 if( !dst ) dst = new VFrame(src_w, src_h, BC_YUV888);
468                 dst->copy_from(src);
469                 int sz = src_w * src_h;
470                 if( sz_inf != sz ) {  delete [] inf;  inf = 0; }
471                 if( !inf ) inf = new uint8_t[sz_inf=sz];
472                 blur(config.blur_len + 1);
473                 plane_pass(0, config.mode_y);
474                 plane_pass(1, config.mode_u);
475                 plane_pass(2, config.mode_v);
476                 output->transfer_from(dst);
477         }
478         return 0;
479 }
480
481 void DeScratchMain::update_gui()
482 {
483         if( !thread ) return;
484         DeScratchWindow *window = (DeScratchWindow *)thread->get_window();
485         window->lock_window("DeScratchMain::update_gui");
486         if( load_configuration() )
487                 window->update_gui();
488         window->unlock_window();
489 }
490
491 NEW_WINDOW_MACRO(DeScratchMain, DeScratchWindow)
492
493
494 DeScratchWindow::DeScratchWindow(DeScratchMain *plugin)
495  : PluginClientWindow(plugin, 512, 256, 512, 256, 0)
496 {
497         this->plugin = plugin;
498 }
499
500 DeScratchWindow::~DeScratchWindow()
501 {
502 }
503
504 void DeScratchWindow::create_objects()
505 {
506         int x = 10, y = 10;
507         plugin->load_configuration();
508         DeScratchConfig &config = plugin->config;
509
510         BC_Title *title;
511         add_tool(title = new BC_Title(x, y, _("DeScratch:")));
512         y += title->get_h() + 5;
513         int x1 = x, x2 = get_w()/2;
514         add_tool(title = new BC_Title(x1=x, y, _("threshold:")));
515         x1 += title->get_w()+16;
516         add_tool(threshold = new DeScratchISlider(this, x1, y, x2-x1-10, 0,64, &config.threshold));
517         add_tool(title = new BC_Title(x1=x2, y, _("asymmetry:")));
518         x1 += title->get_w()+16;
519         add_tool(asymmetry = new DeScratchISlider(this, x1, y, get_w()-x1-15, 0,64, &config.asymmetry));
520         y += threshold->get_h() + 10;
521
522         add_tool(title = new BC_Title(x1=x, y, _("Mode:")));
523         x1 += title->get_w()+16;
524         add_tool(title = new BC_Title(x1, y, _("y:")));
525         int w1 = title->get_w()+16;
526         add_tool(y_mode = new DeScratchMode(this, (x1+=w1), y, &config.mode_y));
527         y_mode->create_objects();  x1 += y_mode->get_w()+16;
528         add_tool(title = new BC_Title(x1, y, _("u:")));
529         add_tool(u_mode = new DeScratchMode(this, (x1+=w1), y, &config.mode_u));
530         u_mode->create_objects();  x1 += u_mode->get_w()+16;
531         add_tool(title = new BC_Title(x1, y, _("v:")));
532         add_tool(v_mode = new DeScratchMode(this, (x1+=w1), y, &config.mode_v));
533         v_mode->create_objects();
534         y += y_mode->get_h() + 10;
535
536         add_tool(title = new BC_Title(x1=x, y, _("width:")));
537         w1 = title->get_w()+16;  x1 += w1;
538         add_tool(title = new BC_Title(x1, y, _("min:")));
539         x1 += title->get_w()+16;
540         add_tool(min_width = new DeScratchISlider(this, x1, y, x2-x1-10, 0,16, &config.min_width));
541         add_tool(title = new BC_Title(x1=x2, y, _("max:")));
542         x1 += title->get_w()+16;
543         add_tool(max_width = new DeScratchISlider(this, x1, y, get_w()-x1-15, 0,16, &config.max_width));
544         y += min_width->get_h() + 10;
545
546         add_tool(title = new BC_Title(x1=x, y, _("len:")));
547         w1 = title->get_w()+16;  x1 += w1;
548         add_tool(title = new BC_Title(x1, y, _("min:")));
549         x1 += title->get_w()+16;
550         add_tool(min_len = new DeScratchFSlider(this, x1, y, x2-x1-10, 0.0,100.0, &config.min_len));
551         add_tool(title = new BC_Title(x1=x2, y, _("max:")));
552         x1 += title->get_w()+16;
553         add_tool(max_len = new DeScratchFSlider(this, x1, y, get_w()-x1-15, 0.0,100.0, &config.max_len));
554         y += min_len->get_h() + 10;
555
556         add_tool(title = new BC_Title(x1=x, y, _("len:")));
557         w1 = title->get_w()+16;  x1 += w1;
558         add_tool(title = new BC_Title(x1, y, _("blur:")));
559         x1 += title->get_w()+16;
560         add_tool(blur_len = new DeScratchISlider(this, x1, y, x2-x1-10, 0,16, &config.blur_len));
561         add_tool(title = new BC_Title(x1=x2, y, _("gap:")));
562         x1 += title->get_w()+16;
563         add_tool(gap_len = new DeScratchFSlider(this, x1, y, get_w()-x1-15, 0.0,100.0, &config.gap_len));
564         y += blur_len->get_h() + 10;
565
566         add_tool(title = new BC_Title(x1=x, y, _("max angle:")));
567         w1 = title->get_w()+16;  x1 += w1;
568         add_tool(max_angle = new DeScratchFSlider(this, x1, y, x2-x1-10, 0.0,90.0, &config.max_angle));
569         add_tool(title = new BC_Title(x1=x2, y, _("fade:")));
570         x1 += title->get_w()+16;
571         add_tool(ffade = new DeScratchFSlider(this, x1, y, get_w()-x1-15, 0.0,100.0, &config.ffade));
572         y += max_angle->get_h() + 10;
573
574         add_tool(title = new BC_Title(x1=x, y, _("border:")));
575         x1 += title->get_w()+16;
576         add_tool(border = new DeScratchISlider(this, x1, y, x2-x1-10, 0,16, &config.border));
577         add_tool(mark = new DeScratchMark(this, x1=x2, y));
578         w1 = DeScratchReset::calculate_w(this, _("Reset"));
579         add_tool(reset = new DeScratchReset(this, get_w()-w1-15, y));
580
581         show_window();
582 }
583
584 void DeScratchWindow::update_gui()
585 {
586         DeScratchConfig &config = plugin->config;
587         y_mode->update(config.mode_y);
588         u_mode->update(config.mode_u);
589         v_mode->update(config.mode_v);
590         min_width->update(config.min_width);
591         max_width->update(config.max_width);
592         min_len->update(config.min_len);
593         max_len->update(config.max_len);
594         blur_len->update(config.blur_len);
595         gap_len->update(config.gap_len);
596         max_angle->update(config.max_angle);
597         ffade->update(config.ffade);
598         mark->update(config.mark);
599 }
600
601
602 DeScratchModeItem::DeScratchModeItem(DeScratchMode *popup, int type, const char *text)
603  : BC_MenuItem(text)
604 {
605         this->popup = popup;
606         this->type = type;
607 }
608
609 DeScratchModeItem::~DeScratchModeItem()
610 {
611 }
612
613 int DeScratchModeItem::handle_event()
614 {
615         popup->update(type);
616         return popup->handle_event();
617 }
618
619 DeScratchMode::DeScratchMode(DeScratchWindow *win, int x, int y, int *value)
620  : BC_PopupMenu(x, y, 64, "", 1)
621 {
622         this->win = win;
623         this->value = value;
624 }
625
626 DeScratchMode::~DeScratchMode()
627 {
628 }
629
630 void DeScratchMode::create_objects()
631 {
632         add_item(new DeScratchModeItem(this, MODE_NONE, _("None")));
633         add_item(new DeScratchModeItem(this, MODE_LOW,  _("Low")));
634         add_item(new DeScratchModeItem(this, MODE_HIGH, _("High")));
635         add_item(new DeScratchModeItem(this, MODE_ALL,  _("All")));
636         set_value(*value);
637 }
638
639 int DeScratchMode::handle_event()
640 {
641         win->plugin->send_configure_change();
642         return 1;
643 }
644
645 void DeScratchMode::update(int v)
646 {
647         set_value(*value = v);
648 }
649
650 void DeScratchMode::set_value(int v)
651 {
652         int i = total_items();
653         while( --i >= 0 && ((DeScratchModeItem*)get_item(i))->type != v );
654         if( i >= 0 ) set_text(get_item(i)->get_text());
655 }
656
657 DeScratchISlider::DeScratchISlider(DeScratchWindow *win,
658                 int x, int y, int w, int min, int max, int *output)
659  : BC_ISlider(x, y, 0, w, w, min, max, *output)
660 {
661         this->win = win;
662         this->output = output;
663 }
664
665 DeScratchISlider::~DeScratchISlider()
666 {
667 }
668
669 int DeScratchISlider::handle_event()
670 {
671         *output = get_value();
672         win->plugin->send_configure_change();
673         return 1;
674 }
675
676 DeScratchFSlider::DeScratchFSlider(DeScratchWindow *win,
677                 int x, int y, int w, float min, float max, float *output)
678  : BC_FSlider(x, y, 0, w, w, min, max, *output)
679 {
680         this->win = win;
681         this->output = output;
682 }
683
684 DeScratchFSlider::~DeScratchFSlider()
685 {
686 }
687
688 int DeScratchFSlider::handle_event()
689 {
690         *output = get_value();
691         win->plugin->send_configure_change();
692         return 1;
693 }
694
695 DeScratchMark::DeScratchMark(DeScratchWindow *win, int x, int y)
696  : BC_CheckBox(x, y, &win->plugin->config.mark, _("Mark"))
697 {
698         this->win = win;
699 };
700
701 DeScratchMark::~DeScratchMark()
702 {
703 }
704
705 int DeScratchMark::handle_event()
706 {
707         int ret = BC_CheckBox::handle_event();
708         win->plugin->send_configure_change();
709         return ret;
710 }
711
712 DeScratchReset::DeScratchReset(DeScratchWindow *win, int x, int y)
713  : BC_GenericButton(x, y, _("Reset"))
714 {
715         this->win = win;
716 }
717
718 int DeScratchReset::handle_event()
719 {
720         win->plugin->config.reset();
721         win->update_gui();
722         win->plugin->send_configure_change();
723         return 1;
724 }
725