rework edge plugin, add crikey plugin, text tumbler tweak, eyedropper coords
[goodguy/history.git] / cinelerra-5.1 / plugins / crikey / crikey.C
1 /*
2  * CINELERRA
3  * Copyright (C) 1997-2015 Adam Williams <broadcast at earthling dot net>
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
21 #include<stdio.h>
22 #include<stdint.h>
23 #include<math.h>
24 #include<string.h>
25
26 #include "arraylist.h"
27 #include "bccmodels.h"
28 #include "cicolors.h"
29 #include "clip.h"
30 #include "filexml.h"
31 #include "crikey.h"
32 #include "crikeywindow.h"
33 #include "language.h"
34 #include "vframe.h"
35
36 // chroma interpolated key, crikey
37
38 REGISTER_PLUGIN(CriKey)
39
40 #if 0
41 void crikey_pgm(const char *fn,VFrame *vfrm)
42 {
43         FILE *fp = fopen(fn,"w");
44         int w = vfrm->get_w(), h = vfrm->get_h();
45         fprintf(fp,"P5\n%d %d\n255\n",w,h);
46         fwrite(vfrm->get_data(),w,h,fp);
47         fclose(fp);
48 }
49 #endif
50
51 CriKeyConfig::CriKeyConfig()
52 {
53         color = 0x000000;
54         threshold = 0.5f;
55         draw_mode = DRAW_ALPHA;
56         key_mode = KEY_SEARCH;
57         point_x = point_y = 0;
58         drag = 0;
59 }
60
61 int CriKeyConfig::equivalent(CriKeyConfig &that)
62 {
63         return this->color != that.color ||
64                 !EQUIV(this->threshold, that.threshold) ||
65                 this->draw_mode != that.draw_mode ||
66                 this->key_mode != that.key_mode ||
67                 !EQUIV(this->point_x, that.point_x) ||
68                 !EQUIV(this->point_y, that.point_y) ||
69                 this->drag != that.drag ? 0 : 1;
70 }
71
72 void CriKeyConfig::copy_from(CriKeyConfig &that)
73 {
74         this->color = that.color;
75         this->threshold = that.threshold;
76         this->draw_mode = that.draw_mode;
77         this->key_mode = that.key_mode;
78         this->point_x = that.point_x;
79         this->point_y = that.point_y;
80         this->drag = that.drag;
81 }
82
83 void CriKeyConfig::interpolate(CriKeyConfig &prev, CriKeyConfig &next,
84                 long prev_frame, long next_frame, long current_frame)
85 {
86         copy_from(prev);
87         double next_scale = (double)(current_frame - prev_frame) / (next_frame - prev_frame);
88         double prev_scale = (double)(next_frame - current_frame) / (next_frame - prev_frame);
89         this->threshold = prev.threshold * prev_scale + next.threshold * next_scale;
90         switch( prev.key_mode ) {
91         case KEY_POINT: {
92                 this->point_x = prev.point_x * prev_scale + next.point_x * next_scale;
93                 this->point_y = prev.point_y * prev_scale + next.point_y * next_scale;
94                 break; }
95         case KEY_SEARCH:
96         case KEY_SEARCH_ALL: {
97                 float prev_target[3];  set_target(0, prev.color, prev_target);
98                 float next_target[3];  set_target(0, next.color, next_target);
99                 float target[3];  // interpolates rgb components
100                 for( int i=0; i<3; ++i )
101                         target[i] = prev_target[i] * prev_scale + next_target[i] * next_scale;
102                 set_color(0, target, this->color);
103                 break; }
104         }
105 }
106
107 void CriKeyConfig::limits()
108 {
109         bclamp(threshold, 0.0f, 1.0f);
110         bclamp(draw_mode, 0, DRAW_MODES-1);
111         bclamp(key_mode,  0, KEY_MODES-1);
112 }
113
114
115 class FillRegion
116 {
117         class segment { public: int y, lt, rt; };
118         ArrayList<segment> stack;
119
120         void push(int y, int lt, int rt) {
121                 segment &seg = stack.append();
122                 seg.y = y;  seg.lt = lt;  seg.rt = rt;
123         }
124         void pop(int &y, int &lt, int &rt) {
125                 segment &seg = stack.last();
126                 y = seg.y;  lt = seg.lt;  rt = seg.rt;
127                 stack.remove();
128         }
129  
130         int w, h, threshold;
131         uint8_t *data, *mask;
132         bool edge_pixel(uint8_t *dp) { return *dp; }
133
134 public:
135         void fill(int x, int y);
136         void set_threshold(float v) { threshold = v; }
137
138         FillRegion(VFrame *d, VFrame *m);
139 };
140
141 FillRegion::FillRegion(VFrame *d, VFrame *m)
142 {
143         threshold = 128;
144         w = d->get_w();
145         h = d->get_h();
146         data = d->get_data();
147         mask = m->get_data();
148 }
149
150 void FillRegion::fill(int x, int y)
151 {
152         push(y, x, x);
153
154         do {
155                 int ilt, irt, lt, rt;
156                 pop(y, ilt, irt);
157                 int ofs = y*w + ilt;
158                 uint8_t *idp = data + ofs, *dp;
159                 uint8_t *imp = mask + ofs, *mp;
160                 for( int x=ilt; x<=irt; ++x,++imp,++idp ) {
161                         if( !*imp || edge_pixel(idp) ) continue;
162                         *imp = 0;
163                         lt = rt = x;
164                         dp = idp;  mp = imp;
165                         for( int i=lt; --i>=0; lt=i,*mp=0 )
166                                 if( !*--mp || edge_pixel(--dp) ) break;
167                         dp = idp;  mp = imp;
168                         for( int i=rt; ++i< w; rt=i,*mp=0 )
169                                 if( !*++mp || edge_pixel(++dp) ) break;
170                         if( y+1 <  h ) push(y+1, lt, rt);
171                         if( y-1 >= 0 ) push(y-1, lt, rt);
172                 }
173         } while( stack.size() > 0 );
174 }
175
176
177 CriKey::CriKey(PluginServer *server)
178  : PluginVClient(server)
179 {
180         engine = 0;
181         msk = 0;
182         dst = 0;
183         diff_pixel = 0;
184 }
185
186 CriKey::~CriKey()
187 {
188         delete engine;
189         delete msk;
190         delete dst;
191 }
192
193 void CriKeyConfig::set_target(int is_yuv, int color, float *target)
194 {
195         float r = ((color>>16) & 0xff) / 255.0f;
196         float g = ((color>> 8) & 0xff) / 255.0f;
197         float b = ((color>> 0) & 0xff) / 255.0f;
198         if( is_yuv ) {
199                 float y, u, v;
200                 YUV::rgb_to_yuv_f(r,g,b, y,u,v);
201                 target[0] = y;
202                 target[1] = u + 0.5f;
203                 target[2] = v + 0.5f;
204         }
205         else {
206                 target[0] = r;
207                 target[1] = g;
208                 target[2] = b;
209         }
210 }
211 void CriKeyConfig::set_color(int is_yuv, float *target, int &color)
212 {
213         float r = target[0];
214         float g = target[1];
215         float b = target[2];
216         if( is_yuv ) {
217                 float y = r, u = g-0.5f, v = b-0.5f;
218                 YUV::yuv_to_rgb_f(y,u,v, r,g,b);
219         }
220         int ir = r >= 1 ? 0xff : r < 0 ? 0 : (int)(r * 256);
221         int ig = g >= 1 ? 0xff : g < 0 ? 0 : (int)(g * 256);
222         int ib = b >= 1 ? 0xff : b < 0 ? 0 : (int)(b * 256);
223         color = (ir << 16) | (ig << 8) | (ib << 0);
224 }
225
226 void CriKey::get_color(int x, int y)
227 {
228         if( x < 0 || x >= w ) return;
229         if( y < 0 || y >= h ) return;
230         uint8_t **src_rows = src->get_rows();
231         uint8_t *sp = src_rows[y] + x*bpp;
232         if( is_float ) {
233                 float *fp = (float *)sp;
234                 for( int i=0; i<comp; ++i,++fp )
235                         target[i] = *fp;
236         }
237         else {
238                 float scale = 1./255;
239                 for( int i=0; i<comp; ++i,++sp )
240                         target[i] = *sp * scale;
241         }
242 }
243
244 float CriKey::diff_uint8(uint8_t *dp)
245 {
246         float scale = 1./255., v = 0;
247         for( int i=0; i<comp; ++i,++dp )
248                 v += fabs(*dp * scale - target[i]);
249         return v / comp;
250 }
251 float CriKey::diff_float(uint8_t *dp)
252 {
253         float *fp = (float *)dp, v = 0;
254         for( int i=0; i<comp; ++i,++fp )
255                 v += fabs(*fp - target[i]);
256         return v / comp;
257 }
258
259 void CriKey::min_key(int &ix, int &iy)
260 {
261         float mv = 1;
262         uint8_t **src_rows = src->get_rows();
263         for( int y=0; y<h; ++y ) {
264                 uint8_t *sp = src_rows[y];
265                 for( int x=0; x<w; ++x,sp+=bpp ) {
266                         float v = (this->*diff_pixel)(sp);
267                         if( v >= mv ) continue;
268                         mv = v;  ix = x;  iy = y;
269                 }
270         }
271 }
272 bool CriKey::find_key(int &ix, int &iy, float thr)
273 {
274         uint8_t **src_rows = src->get_rows();
275         uint8_t **msk_rows = msk->get_rows();
276         int x = ix, y = iy;
277         for( ; y<h; ++y ) {
278                 uint8_t *sp = src_rows[y] + x*bpp;
279                 uint8_t *mp = msk_rows[y] + x;
280                 for( ; x<w; ++x,++mp,sp+=bpp ) {
281                         if( !*mp ) continue;
282                         float v = (this->*diff_pixel)(sp);
283                         if( v < thr ) {
284                                 ix = x;  iy = y;
285                                 return true;
286                         }
287                 }
288                 x = 0;
289         }
290         return false;
291 }
292
293 const char* CriKey::plugin_title() { return _("CriKey"); }
294 int CriKey::is_realtime() { return 1; }
295
296 NEW_WINDOW_MACRO(CriKey, CriKeyWindow);
297 LOAD_CONFIGURATION_MACRO(CriKey, CriKeyConfig)
298
299 void CriKey::save_data(KeyFrame *keyframe)
300 {
301         FileXML output;
302
303 // cause data to be stored directly in text
304         output.set_shared_output(keyframe->get_data(), MESSAGESIZE);
305
306         output.tag.set_title("CRIKEY");
307         output.tag.set_property("COLOR", config.color);
308         output.tag.set_property("THRESHOLD", config.threshold);
309         output.tag.set_property("DRAW_MODE", config.draw_mode);
310         output.tag.set_property("KEY_MODE", config.key_mode);
311         output.tag.set_property("POINT_X", config.point_x);
312         output.tag.set_property("POINT_Y", config.point_y);
313         output.tag.set_property("DRAG", config.drag);
314         output.append_tag();
315         output.append_newline();
316         output.tag.set_title("/CRIKEY");
317         output.append_tag();
318         output.append_newline();
319         output.terminate_string();
320 }
321
322 void CriKey::read_data(KeyFrame *keyframe)
323 {
324         FileXML input;
325         input.set_shared_input(keyframe->get_data(), strlen(keyframe->get_data()));
326         int result = 0;
327
328         while( !(result=input.read_tag()) ) {
329                 if(input.tag.title_is("CRIKEY")) {
330                         config.color = input.tag.get_property("COLOR", config.color);
331                         config.threshold = input.tag.get_property("THRESHOLD", config.threshold);
332                         config.draw_mode = input.tag.get_property("DRAW_MODE", config.draw_mode);
333                         config.key_mode = input.tag.get_property("KEY_MODE", config.key_mode);
334                         config.point_x = input.tag.get_property("POINT_X", config.point_x);
335                         config.point_y = input.tag.get_property("POINT_Y", config.point_y);
336                         config.drag = input.tag.get_property("DRAG", config.drag);
337                         config.limits();
338                 }
339                 else if(input.tag.title_is("/CRIKEY")) {
340                         result = 1;
341                 }
342         }
343
344 }
345
346 void CriKey::update_gui()
347 {
348         if( !thread ) return;
349         if( !load_configuration() ) return;
350         thread->window->lock_window("CriKey::update_gui");
351         CriKeyWindow *window = (CriKeyWindow*)thread->window;
352         window->update_gui();
353         window->flush();
354         thread->window->unlock_window();
355 }
356
357 void CriKey::draw_alpha(VFrame *msk)
358 {
359         uint8_t **src_rows = src->get_rows();
360         uint8_t **msk_rows = msk->get_rows();
361         switch( color_model ) {
362         case BC_RGB_FLOAT:
363                 for( int y=0; y<h; ++y ) {
364                         uint8_t *sp = src_rows[y], *mp = msk_rows[y];
365                         for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
366                                 if( *mp ) continue;
367                                 float *px = (float *)sp;
368                                 px[0] = px[1] = px[2] = 0;
369                         }
370                 }
371                 break;
372         case BC_RGBA_FLOAT:
373                 for( int y=0; y<h; ++y ) {
374                         uint8_t *sp = src_rows[y], *mp = msk_rows[y];
375                         for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
376                                 if( *mp ) continue;
377                                 float *px = (float *)sp;
378                                 px[3] = 0;
379                         }
380                 }
381                 break;
382         case BC_RGB888:
383                 for( int y=0; y<h; ++y ) {
384                         uint8_t *sp = src_rows[y], *mp = msk_rows[y];
385                         for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
386                                 if( *mp ) continue;
387                                 uint8_t *px = sp;
388                                 px[0] = px[1] = px[2] = 0;
389                         }
390                 }
391                 break;
392         case BC_YUV888:
393                 for( int y=0; y<h; ++y ) {
394                         uint8_t *sp = src_rows[y], *mp = msk_rows[y];
395                         for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
396                                 if( *mp ) continue;
397                                 uint8_t *px = sp;
398                                 px[0] = 0;
399                                 px[1] = px[2] = 0x80;
400                         }
401                 }
402                 break;
403         case BC_RGBA8888:
404         case BC_YUVA8888:
405                 for( int y=0; y<h; ++y ) {
406                         uint8_t *sp = src_rows[y], *mp = msk_rows[y];
407                         for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
408                                 if( *mp ) continue;
409                                 uint8_t *px = sp;
410                                 px[3] = 0;
411                         }
412                 }
413                 break;
414         }
415 }
416
417 void CriKey::draw_mask(VFrame *msk)
418 {
419         uint8_t **src_rows = src->get_rows();
420         uint8_t **msk_rows = msk->get_rows();
421         float scale = 1 / 255.0f;
422         switch( color_model ) {
423         case BC_RGB_FLOAT:
424                 for( int y=0; y<h; ++y ) {
425                         uint8_t *sp = src_rows[y], *mp = msk_rows[y];
426                         for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
427                                 float a = *mp * scale;
428                                 float *px = (float *)sp;
429                                 px[0] = px[1] = px[2] = a;
430                         }
431                 }
432                 break;
433         case BC_RGBA_FLOAT:
434                 for( int y=0; y<h; ++y ) {
435                         uint8_t *sp = src_rows[y], *mp = msk_rows[y];
436                         for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
437                                 float a = *mp * scale;
438                                 float *px = (float *)sp;
439                                 px[0] = px[1] = px[2] = a;
440                                 px[3] = 1.0f;
441                         }
442                 }
443                 break;
444         case BC_RGB888:
445                 for( int y=0; y<h; ++y ) {
446                         uint8_t *sp = src_rows[y], *mp = msk_rows[y];
447                         for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
448                                 float a = *mp * scale;
449                                 uint8_t *px = sp;
450                                 px[0] = px[1] = px[2] = a * 255;
451                         }
452                 }
453                 break;
454         case BC_RGBA8888:
455                 for( int y=0; y<h; ++y ) {
456                         uint8_t *sp = src_rows[y], *mp = msk_rows[y];
457                         for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
458                                 float a = *mp * scale;
459                                 uint8_t *px = sp;
460                                 px[0] = px[1] = px[2] = a * 255;
461                                 px[3] = 0xff;
462                         }
463                 }
464                 break;
465         case BC_YUV888:
466                 for( int y=0; y<h; ++y ) {
467                         uint8_t *sp = src_rows[y], *mp = msk_rows[y];
468                         for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
469                                 float a = *mp * scale;
470                                 uint8_t *px = sp;
471                                 px[0] = a * 255;
472                                 px[1] = px[2] = 0x80;
473                         }
474                 }
475                 break;
476         case BC_YUVA8888:
477                 for( int y=0; y<h; ++y ) {
478                         uint8_t *sp = src_rows[y], *mp = msk_rows[y];
479                         for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
480                                 float a = *mp * scale;
481                                 uint8_t *px = sp;
482                                 px[0] = a * 255;
483                                 px[1] = px[2] = 0x80;
484                                 px[3] = 0xff;
485                         }
486                 }
487                 break;
488         }
489 }
490
491 int CriKey::process_buffer(VFrame *frame, int64_t start_position, double frame_rate)
492 {
493         load_configuration();
494         src = frame;
495         w = src->get_w(), h = src->get_h();
496         color_model = src->get_color_model();
497         bpp = BC_CModels::calculate_pixelsize(color_model);
498         comp = BC_CModels::components(color_model);
499         if( comp > 3 ) comp = 3;
500         is_yuv = BC_CModels::is_yuv(color_model);
501         is_float = BC_CModels::is_float(color_model);
502         diff_pixel = is_float ? &CriKey::diff_float : &CriKey::diff_uint8;
503
504         read_frame(src, 0, start_position, frame_rate, 0);
505
506         switch( config.key_mode ) {
507         case KEY_SEARCH:
508         case KEY_SEARCH_ALL:
509                 set_target(is_yuv, config.color, target);
510                 break;
511         case KEY_POINT:
512                 get_color(config.point_x, config.point_y);
513                 break;
514         }
515
516         if( dst && ( dst->get_w() != w || src->get_h() != h ) ) {
517                 delete dst;  dst = 0;
518         }
519         if( !dst )
520                 dst = new VFrame(w, h, BC_A8);
521         memset(dst->get_data(), 0x00, dst->get_data_size());
522
523         if( !engine )
524                 engine = new CriKeyEngine(this,
525                         PluginClient::get_project_smp() + 1,
526                         PluginClient::get_project_smp() + 1);
527         engine->process_packages();
528 // copy fill btm/rt edges
529         int w1 = w-1, h1 = h-1;
530         uint8_t *dp = dst->get_data();
531         if( w1 > 0 ) for( int y=0; y<h1; ++y,dp+=w ) dp[w1] = dp[w1-1];
532         if( h1 > 0 ) for( int x=0; x<w; ++x ) dp[x] = dp[x-w];
533 //crikey_pgm("/tmp/dst.pgm",dst);
534
535         if( config.draw_mode == DRAW_EDGE ) {
536                 draw_mask(dst);
537                 return 0;
538         }
539
540         if( msk && ( msk->get_w() != w || msk->get_h() != h ) ) {
541                 delete msk;  msk = 0;
542         }
543         if( !msk )
544                 msk = new VFrame(w, h, BC_A8);
545         memset(msk->get_data(), 0xff, msk->get_data_size());
546
547         FillRegion fill_region(dst, msk);
548         fill_region.set_threshold(config.threshold);
549
550         int x = 0, y = 0;
551         switch( config.key_mode ) {
552         case KEY_SEARCH:
553                 min_key(x, y);
554                 fill_region.fill(x, y);
555                 break;
556         case KEY_SEARCH_ALL:
557                 while( find_key(x, y, config.threshold) ) {
558                         fill_region.fill(x, y);
559                         ++x;
560                 }
561                 break;
562         case KEY_POINT:
563                 x = config.point_x, y = config.point_y;
564                 if( x >= 0 && x < w && y >= 0 && y < h )
565                         fill_region.fill(x, y);
566                 break;
567         }
568 //crikey_pgm("/tmp/msk.pgm",msk);
569
570         if( config.draw_mode == DRAW_MASK ) {
571                 draw_mask(msk);
572                 return 0;
573         }
574
575         draw_alpha(msk);
576         return 0;
577 }
578
579
580 void CriKeyEngine::init_packages()
581 {
582         int y = 0, h1 = plugin->h-1;
583         for(int i = 0; i < get_total_packages(); ) {
584                 CriKeyPackage *pkg = (CriKeyPackage*)get_package(i++);
585                 pkg->y1 = y;
586                 y = h1 * i / LoadServer::get_total_packages();
587                 pkg->y2 = y;
588         }
589 }
590
591 LoadPackage* CriKeyEngine::new_package()
592 {
593         return new CriKeyPackage();
594 }
595
596 LoadClient* CriKeyEngine::new_client()
597 {
598         return new CriKeyUnit(this);
599 }
600
601 #define EDGE_MACRO(type, max, components, is_yuv) \
602 { \
603         uint8_t **src_rows = src->get_rows(); \
604         int comps = MIN(components, 3); \
605         float scale = 1.0f/max; \
606         for( int y=y1; y<y2; ++y ) { \
607                 uint8_t *row0 = src_rows[y], *row1 = src_rows[y+1]; \
608                 uint8_t *outp = dst_rows[y]; \
609                 for( int v,x=x1; x<x2; *outp++=v,++x,row0+=bpp,row1+=bpp ) { \
610                         type *r0 = (type*)row0, *r1 = (type*)row1; \
611                         float a00 = 0, a01 = 0, a10 = 0, a11 = 0; \
612                         for( int i=0; i<comps; ++i,++r0,++r1 ) { \
613                                 float t = target[i]; \
614                                 a00 += fabs(t - r0[0]*scale); \
615                                 a01 += fabs(t - r0[components]*scale); \
616                                 a10 += fabs(t - r1[0]*scale); \
617                                 a11 += fabs(t - r1[components]*scale); \
618                         } \
619                         v = 0; \
620                         float a = bmin(bmin(a00, a01), bmin(a10, a11)); \
621                         if( a < threshold ) continue; \
622                         float b = bmax(bmax(a00, a01), bmax(a10, a11)); \
623                         if( b <= threshold ) continue; \
624                         v = (b-a)*254 + 1; \
625                 } \
626         } \
627 } break
628
629
630 void CriKeyUnit::process_package(LoadPackage *package)
631 {
632         VFrame *src = server->plugin->src;
633         int bpp = server->plugin->bpp;
634         VFrame *dst = server->plugin->dst;
635         uint8_t **dst_rows = dst->get_rows();
636         float *target = server->plugin->target;
637         float threshold = server->plugin->config.threshold;
638         CriKeyPackage *pkg = (CriKeyPackage*)package;
639         int x1 = 0, x2 = server->plugin->w-1;
640         int y1 = pkg->y1, y2 = pkg->y2;
641
642         switch( src->get_color_model() ) {
643         case BC_RGB_FLOAT:  EDGE_MACRO(float, 1, 3, 0);
644         case BC_RGBA_FLOAT: EDGE_MACRO(float, 1, 4, 0);
645         case BC_RGB888:     EDGE_MACRO(unsigned char, 0xff, 3, 0);
646         case BC_YUV888:     EDGE_MACRO(unsigned char, 0xff, 3, 1);
647         case BC_RGBA8888:   EDGE_MACRO(unsigned char, 0xff, 4, 0);
648         case BC_YUVA8888:   EDGE_MACRO(unsigned char, 0xff, 4, 1);
649         }
650 }
651