3 * Copyright (C) 1997-2015 Adam Williams <broadcast at earthling dot net>
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.
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.
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
26 #include "arraylist.h"
27 #include "bccmodels.h"
30 #include "edlsession.h"
33 #include "crikeywindow.h"
37 // chroma interpolated key, crikey
39 REGISTER_PLUGIN(CriKey)
42 void crikey_pgm(const char *fn,VFrame *vfrm)
44 FILE *fp = fopen(fn,"w");
45 int w = vfrm->get_w(), h = vfrm->get_h();
46 fprintf(fp,"P5\n%d %d\n255\n",w,h);
47 fwrite(vfrm->get_data(),w,h,fp);
52 CriKeyPoint::CriKeyPoint(int tag, int e, float x, float y, float t)
54 this->tag = tag; this->e = e;
55 this->x = x; this->y = y;
58 CriKeyPoint::~CriKeyPoint()
62 CriKeyConfig::CriKeyConfig()
65 draw_mode = DRAW_ALPHA;
69 CriKeyConfig::~CriKeyConfig()
73 int CriKeyConfig::equivalent(CriKeyConfig &that)
75 if( !EQUIV(this->threshold, that.threshold) ) return 0;
76 if( this->draw_mode != that.draw_mode ) return 0;
77 if( this->drag != that.drag ) return 0;
78 if( this->points.size() != that.points.size() ) return 0;
79 for( int i=0, n=points.size(); i<n; ++i ) {
80 CriKeyPoint *ap = this->points[i], *bp = that.points[i];
81 if( ap->tag != bp->tag ) return 0;
82 if( ap->e != bp->e ) return 0;
83 if( !EQUIV(ap->x, bp->x) ) return 0;
84 if( !EQUIV(ap->y, bp->y) ) return 0;
85 if( !EQUIV(ap->t, bp->t) ) return 0;
90 void CriKeyConfig::copy_from(CriKeyConfig &that)
92 this->threshold = that.threshold;
93 this->draw_mode = that.draw_mode;
94 this->drag = that.drag;
95 this->selected = that.selected;
97 points.remove_all_objects();
98 for( int i=0,n=that.points.size(); i<n; ++i ) {
99 CriKeyPoint *pt = that.points[i];
100 add_point(pt->tag, pt->e, pt->x, pt->y, pt->t);
104 void CriKeyConfig::interpolate(CriKeyConfig &prev, CriKeyConfig &next,
105 long prev_frame, long next_frame, long current_frame)
107 this->threshold = prev.threshold;
108 this->draw_mode = prev.draw_mode;
109 this->drag = prev.drag;
110 this->selected = prev.selected;
112 double next_scale = (double)(current_frame - prev_frame) / (next_frame - prev_frame);
113 double prev_scale = (double)(next_frame - current_frame) / (next_frame - prev_frame);
115 points.remove_all_objects();
116 int prev_sz = prev.points.size(), next_sz = next.points.size();
117 for( int i=0; i<prev_sz; ++i ) {
118 CriKeyPoint *pt = prev.points[i], *nt = 0;
119 float x = pt->x, y = pt->y, t = pt->t;
120 int k = next_sz; // associated by tag id in next
121 while( --k >= 0 && pt->tag != (nt=next.points[k])->tag );
123 x = x * prev_scale + nt->x * next_scale;
124 y = y * prev_scale + nt->y * next_scale;
125 t = t * prev_scale + nt->t * next_scale;
127 add_point(pt->tag, pt->e, x, y, t);
131 void CriKeyConfig::limits()
133 bclamp(threshold, 0.0f, 1.0f);
134 bclamp(draw_mode, 0, DRAW_MODES-1);
137 int CriKeyConfig::add_point(int tag, int e, float x, float y, float t)
139 int k = points.size();
142 for( int i=k; --i>=0; ) {
143 int n = points[i]->tag;
144 if( n >= tag ) tag = n + 1;
147 points.append(new CriKeyPoint(tag, e, x, y, t));
151 void CriKeyConfig::del_point(int i)
153 points.remove_object_number(i);
159 class segment { public: int y, lt, rt; };
160 ArrayList<segment> stack;
162 void push(int y, int lt, int rt) {
163 segment &seg = stack.append();
164 seg.y = y; seg.lt = lt; seg.rt = rt;
166 void pop(int &y, int <, int &rt) {
167 segment &seg = stack.last();
168 y = seg.y; lt = seg.lt; rt = seg.rt;
175 bool edge_pixel(int i) { return edg[i] > 0; }
178 void fill(int x, int y);
181 FillRegion(VFrame *edg, VFrame *msk);
184 FillRegion::FillRegion(VFrame *edg, VFrame *msk)
186 this->w = msk->get_w();
187 this->h = msk->get_h();
188 this->msk = (uint8_t*) msk->get_data();
189 this->edg = (float*) edg->get_data();
192 void FillRegion::fill(int x, int y)
197 void FillRegion::run()
199 while( stack.size() > 0 ) {
203 for( int x=ilt; x<=irt; ++x,++ofs ) {
204 if( !msk[ofs] ) continue;
206 if( edge_pixel(ofs) ) continue;
209 for( int i=lt; --i>=0; ) {
210 if( !msk[--lofs] ) break;
211 msk[lofs] = 0; lt = i;
212 if( edge_pixel(lofs) ) break;
215 for( int i=rt; ++i< w; rt=i,msk[rofs]=0 ) {
216 if( !msk[++rofs] ) break;
217 msk[rofs] = 0; rt = i;
218 if( edge_pixel(rofs) ) break;
220 if( y+1 < h ) push(y+1, lt, rt);
221 if( y-1 >= 0 ) push(y-1, lt, rt);
227 CriKey::CriKey(PluginServer *server)
228 : PluginVClient(server)
242 int CriKey::set_target(float *color, int x, int y)
244 if( x < 0 || x >= w ) return 1;
245 if( y < 0 || y >= h ) return 1;
246 uint8_t **src_rows = src->get_rows();
248 float *fp = (float*)(src_rows[y] + x*bpp);
249 for( int i=0; i<comp; ++i,++fp ) color[i] = *fp;
252 uint8_t *sp = src_rows[y] + x*bpp;
253 for( int i=0; i<comp; ++i,++sp ) color[i] = *sp;
258 const char* CriKey::plugin_title() { return N_("CriKey"); }
259 int CriKey::is_realtime() { return 1; }
261 NEW_WINDOW_MACRO(CriKey, CriKeyWindow);
262 LOAD_CONFIGURATION_MACRO(CriKey, CriKeyConfig)
264 int CriKey::new_point()
266 EDLSession *session = get_edlsession();
267 float x = !session ? 0.f : session->output_w / 2.f;
268 float y = !session ? 0.f : session->output_h / 2.f;
269 return config.add_point(-1, 0, x, y, 0.5f);
272 void CriKey::save_data(KeyFrame *keyframe)
276 // cause data to be stored directly in text
277 output.set_shared_output(keyframe->get_data(), MESSAGESIZE);
279 output.tag.set_title("CRIKEY");
280 output.tag.set_property("THRESHOLD", config.threshold);
281 output.tag.set_property("DRAW_MODE", config.draw_mode);
282 output.tag.set_property("DRAG", config.drag);
283 output.tag.set_property("SELECTED", config.selected);
285 output.append_newline();
286 output.tag.set_title("/CRIKEY");
288 output.append_newline();
289 for( int i=0, n = config.points.size(); i<n; ++i ) {
290 CriKeyPoint *pt = config.points[i];
291 char point[BCSTRLEN];
292 sprintf(point,"/POINT_%d",pt->tag);
293 output.tag.set_title(point+1);
294 output.tag.set_property("E", pt->e);
295 output.tag.set_property("X", pt->x);
296 output.tag.set_property("Y", pt->y);
297 output.tag.set_property("T", pt->t);
299 output.tag.set_title(point+0);
301 output.append_newline();
303 output.terminate_string();
306 void CriKey::read_data(KeyFrame *keyframe)
309 input.set_shared_input(keyframe->get_data(), strlen(keyframe->get_data()));
310 config.points.remove_all_objects();
313 while( !(result=input.read_tag()) ) {
314 if( input.tag.title_is("CRIKEY") ) {
315 config.threshold = input.tag.get_property("THRESHOLD", config.threshold);
316 config.draw_mode = input.tag.get_property("DRAW_MODE", config.draw_mode);
317 config.drag = input.tag.get_property("DRAG", config.drag);
318 config.selected = input.tag.get_property("SELECTED", 0);
321 else if( !strncmp(input.tag.get_title(),"POINT_",6) ) {
322 int tag = atoi(input.tag.get_title() + 6);
323 int e = input.tag.get_property("E", 0);
324 float x = input.tag.get_property("X", 0.f);
325 float y = input.tag.get_property("Y", 0.f);
326 float t = input.tag.get_property("T", .5f);
327 config.add_point(tag, e, x, y, t);
331 if( !config.points.size() ) new_point();
334 void CriKey::update_gui()
336 if( !thread ) return;
337 if( !load_configuration() ) return;
338 thread->window->lock_window("CriKey::update_gui");
339 CriKeyWindow *window = (CriKeyWindow*)thread->window;
340 window->update_gui();
342 thread->window->unlock_window();
345 void CriKey::draw_alpha(VFrame *msk)
347 uint8_t **src_rows = src->get_rows();
348 uint8_t **msk_rows = msk->get_rows();
349 switch( color_model ) {
351 for( int y=0; y<h; ++y ) {
352 uint8_t *sp = src_rows[y], *mp = msk_rows[y];
353 for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
355 float *px = (float *)sp;
356 px[0] = px[1] = px[2] = 0;
361 for( int y=0; y<h; ++y ) {
362 uint8_t *sp = src_rows[y], *mp = msk_rows[y];
363 for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
365 float *px = (float *)sp;
371 for( int y=0; y<h; ++y ) {
372 uint8_t *sp = src_rows[y], *mp = msk_rows[y];
373 for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
376 px[0] = px[1] = px[2] = 0;
381 for( int y=0; y<h; ++y ) {
382 uint8_t *sp = src_rows[y], *mp = msk_rows[y];
383 for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
387 px[1] = px[2] = 0x80;
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 ) {
405 void CriKey::draw_edge(VFrame *edg)
407 uint8_t **src_rows = src->get_rows();
408 float **edg_rows = (float**) edg->get_rows(), gain = 10;
409 switch( color_model ) {
411 for( int y=0; y<h; ++y ) {
412 uint8_t *sp = src_rows[y];
413 float *ap = edg_rows[y];
414 for( int x=0; x<w; ++x,++ap,sp+=bpp ) {
415 float *px = (float *)sp, v = *ap * gain;
416 px[0] = px[1] = px[2] = v<1 ? v : 1;
421 for( int y=0; y<h; ++y ) {
422 uint8_t *sp = src_rows[y];
423 float *ap = edg_rows[y];
424 for( int x=0; x<w; ++x,++ap,sp+=bpp ) {
425 float *px = (float *)sp, v = *ap * gain;
426 px[0] = px[1] = px[2] = v<1 ? v : 1;
432 for( int y=0; y<h; ++y ) {
433 uint8_t *sp = src_rows[y];
434 float *ap = edg_rows[y];
435 for( int x=0; x<w; ++x,++ap,sp+=bpp ) {
436 uint8_t *px = sp; float v = *ap * gain;
437 px[0] = px[1] = px[2] = v<1 ? v*256 : 255;
442 for( int y=0; y<h; ++y ) {
443 uint8_t *sp = src_rows[y];
444 float *ap = edg_rows[y];
445 for( int x=0; x<w; ++x,++ap,sp+=bpp ) {
446 uint8_t *px = sp; float v = *ap * gain;
447 px[0] = px[1] = px[2] = v<1 ? v*256 : 255;
453 for( int y=0; y<h; ++y ) {
454 uint8_t *sp = src_rows[y];
455 float *ap = edg_rows[y];
456 for( int x=0; x<w; ++x,++ap,sp+=bpp ) {
457 uint8_t *px = sp; float v = *ap * gain;
458 px[0] = *ap<1 ? v*256 : 255;
459 px[1] = px[2] = 0x80;
464 for( int y=0; y<h; ++y ) {
465 uint8_t *sp = src_rows[y];
466 float *ap = edg_rows[y];
467 for( int x=0; x<w; ++x,++ap,sp+=bpp ) {
468 uint8_t *px = sp; float v = *ap * gain;
469 px[0] = *ap<1 ? v*256 : 255;
470 px[1] = px[2] = 0x80;
478 void CriKey::draw_mask(VFrame *msk)
480 uint8_t **src_rows = src->get_rows();
481 uint8_t **msk_rows = msk->get_rows();
482 float scale = 1 / 255.0f;
483 switch( color_model ) {
485 for( int y=0; y<h; ++y ) {
486 uint8_t *sp = src_rows[y], *mp = msk_rows[y];
487 for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
488 float a = *mp * scale;
489 float *px = (float *)sp;
490 px[0] = px[1] = px[2] = a;
495 for( int y=0; y<h; ++y ) {
496 uint8_t *sp = src_rows[y], *mp = msk_rows[y];
497 for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
498 float a = *mp * scale;
499 float *px = (float *)sp;
500 px[0] = px[1] = px[2] = a;
506 for( int y=0; y<h; ++y ) {
507 uint8_t *sp = src_rows[y], *mp = msk_rows[y];
508 for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
509 float a = *mp * scale;
511 px[0] = px[1] = px[2] = a * 255;
516 for( int y=0; y<h; ++y ) {
517 uint8_t *sp = src_rows[y], *mp = msk_rows[y];
518 for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
519 float a = *mp * scale;
521 px[0] = px[1] = px[2] = a * 255;
527 for( int y=0; y<h; ++y ) {
528 uint8_t *sp = src_rows[y], *mp = msk_rows[y];
529 for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
530 float a = *mp * scale;
533 px[1] = px[2] = 0x80;
538 for( int y=0; y<h; ++y ) {
539 uint8_t *sp = src_rows[y], *mp = msk_rows[y];
540 for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
541 float a = *mp * scale;
544 px[1] = px[2] = 0x80;
553 void CriKey::draw_point(VFrame *src, CriKeyPoint *pt)
555 int d = bmax(w,h) / 200 + 2;
556 int r = d/2+1, x = pt->x, y = pt->y;
557 src->draw_smooth(x-r,y+0, x-r, y-r, x+0,y-r);
558 src->draw_smooth(x+0,y-r, x+r, y-r, x+r,y+0);
559 src->draw_smooth(x+r,y+0, x+r, y+r, x+0,y+r);
560 src->draw_smooth(x+0,y+r, x-r, y+r, x-r,y+0);
562 src->set_pixel_color(RED);
563 src->draw_x(pt->x, pt->y, d);
566 src->set_pixel_color(BLUE);
567 src->draw_t(pt->x, pt->y, d);
572 static void get_vframe(VFrame *&vfrm, int w, int h, int color_model)
574 if( vfrm && ( vfrm->get_w() != w || vfrm->get_h() != h ) ) {
575 delete vfrm; vfrm = 0;
577 if( !vfrm ) vfrm = new VFrame(w, h, color_model, 0);
580 static void fill_edge(VFrame *vfrm, int w, int h)
582 int w1 = w-1, h1 = h-1;
583 float *dp = (float*) vfrm->get_data();
584 if( w1 > 0 ) for( int y=0; y<h1; ++y,dp+=w ) dp[w1] = dp[w1-1];
585 if( h1 > 0 ) for( int x=0; x<w; ++x ) dp[x] = dp[x-w];
588 int CriKey::process_buffer(VFrame *frame, int64_t start_position, double frame_rate)
590 load_configuration();
592 w = src->get_w(), h = src->get_h();
593 color_model = src->get_color_model();
594 bpp = BC_CModels::calculate_pixelsize(color_model);
595 is_float = BC_CModels::is_float(color_model);
596 is_yuv = BC_CModels::is_yuv(color_model);
597 comp = BC_CModels::components(color_model);
598 if( comp > 3 ) comp = 3;
600 read_frame(src, 0, start_position, frame_rate, 0);
601 get_vframe(edg, w, h, BC_A_FLOAT);
604 engine = new CriKeyEngine(this,
605 PluginClient::get_project_smp() + 1,
606 PluginClient::get_project_smp() + 1);
608 get_vframe(msk, w, h, BC_A8);
609 memset(msk->get_data(), 0xff, msk->get_data_size());
611 for( int i=0, n=config.points.size(); i<n; ++i ) {
612 CriKeyPoint *pt = config.points[i];
613 if( !pt->e ) continue;
614 if( set_target(engine->color, pt->x, pt->y) ) continue;
615 engine->threshold = pt->t;
617 engine->process_packages();
618 fill_edge(edg, w, h);
619 FillRegion fill_region(edg, msk);
620 fill_region.fill(pt->x, pt->y);
624 //crikey_pgm("/tmp/msk.pgm",msk);
625 switch( config.draw_mode ) {
626 case DRAW_ALPHA: draw_alpha(msk); break;
627 case DRAW_EDGE: draw_edge(edg); break;
628 case DRAW_MASK: draw_mask(msk); break;
632 for( int i=0, n=config.points.size(); i<n; ++i ) {
633 CriKeyPoint *pt = config.points[i];
634 src->set_pixel_color(config.selected == i ? GREEN : WHITE);
642 void CriKeyEngine::init_packages()
644 int y = 0, h1 = plugin->h-1;
645 for(int i = 0; i < get_total_packages(); ) {
646 CriKeyPackage *pkg = (CriKeyPackage*)get_package(i++);
648 y = h1 * i / LoadServer::get_total_packages();
653 LoadPackage* CriKeyEngine::new_package()
655 return new CriKeyPackage();
658 LoadClient* CriKeyEngine::new_client()
660 return new CriKeyUnit(this);
663 #define EDGE_MACRO(type, components, is_yuv) \
665 uint8_t **src_rows = src->get_rows(); \
666 int comps = MIN(components, 3); \
667 for( int y=y1; y<y2; ++y ) { \
668 uint8_t *row0 = src_rows[y], *row1 = src_rows[y+1]; \
669 float *edgp = edg_rows[y]; \
670 for( int x=x1; x<x2; ++edgp,++x,row0+=bpp,row1+=bpp ) { \
671 type *r0 = (type*)row0, *r1 = (type*)row1; \
672 float a00 = 0, a01 = 0, a10 = 0, a11 = 0; \
673 for( int c=0; c<comps; ++c,++r0,++r1 ) { \
674 float t = target_color[c]; \
675 a00 += fabs(t - r0[0]); \
676 a01 += fabs(t - r0[components]); \
677 a10 += fabs(t - r1[0]); \
678 a11 += fabs(t - r1[components]); \
680 float mx = scale * bmax(bmax(a00, a01), bmax(a10, a11)); \
681 if( mx < threshold ) continue; \
682 float mn = scale * bmin(bmin(a00, a01), bmin(a10, a11)); \
683 if( mn >= threshold ) continue; \
684 *edgp += (mx - mn); \
690 void CriKeyUnit::process_package(LoadPackage *package)
692 int color_model = server->plugin->color_model;
693 int bpp = server->plugin->bpp;
694 VFrame *src = server->plugin->src;
695 VFrame *edg = server->plugin->edg;
696 float **edg_rows = (float**)edg->get_rows();
697 float *target_color = server->color;
698 float threshold = 2.f * server->threshold*server->threshold;
699 float scale = 1./BC_CModels::calculate_max(color_model);
700 CriKeyPackage *pkg = (CriKeyPackage*)package;
701 int x1 = 0, x2 = server->plugin->w-1;
702 int y1 = pkg->y1, y2 = pkg->y2;
704 switch( color_model ) {
705 case BC_RGB_FLOAT: EDGE_MACRO(float, 3, 0);
706 case BC_RGBA_FLOAT: EDGE_MACRO(float, 4, 0);
707 case BC_RGB888: EDGE_MACRO(unsigned char, 3, 0);
708 case BC_YUV888: EDGE_MACRO(unsigned char, 3, 1);
709 case BC_RGBA8888: EDGE_MACRO(unsigned char, 4, 0);
710 case BC_YUVA8888: EDGE_MACRO(unsigned char, 4, 1);