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"
31 #include "edlsession.h"
34 #include "crikeywindow.h"
36 #include "keyframes.h"
38 #include "transportque.inc"
41 // chroma interpolated key, crikey
43 REGISTER_PLUGIN(CriKey)
46 void crikey_pgm(const char *fn,VFrame *vfrm)
48 FILE *fp = fopen(fn,"w");
49 int w = vfrm->get_w(), h = vfrm->get_h();
50 fprintf(fp,"P5\n%d %d\n255\n",w,h);
51 fwrite(vfrm->get_data(),w,h,fp);
56 CriKeyPoint::CriKeyPoint(int tag, int e, float x, float y, float t)
58 this->tag = tag; this->e = e;
59 this->x = x; this->y = y;
62 CriKeyPoint::~CriKeyPoint()
66 CriKeyConfig::CriKeyConfig()
69 draw_mode = DRAW_ALPHA;
73 CriKeyConfig::~CriKeyConfig()
77 int CriKeyConfig::equivalent(CriKeyConfig &that)
79 if( !EQUIV(this->threshold, that.threshold) ) return 0;
80 if( this->draw_mode != that.draw_mode ) return 0;
81 if( this->drag != that.drag ) return 0;
82 if( this->points.size() != that.points.size() ) return 0;
83 for( int i=0, n=points.size(); i<n; ++i ) {
84 CriKeyPoint *ap = this->points[i], *bp = that.points[i];
85 if( ap->tag != bp->tag ) return 0;
86 if( ap->e != bp->e ) return 0;
87 if( !EQUIV(ap->x, bp->x) ) return 0;
88 if( !EQUIV(ap->y, bp->y) ) return 0;
89 if( !EQUIV(ap->t, bp->t) ) return 0;
94 void CriKeyConfig::copy_from(CriKeyConfig &that)
96 this->threshold = that.threshold;
97 this->draw_mode = that.draw_mode;
98 this->drag = that.drag;
99 this->selected = that.selected;
101 points.remove_all_objects();
102 for( int i=0,n=that.points.size(); i<n; ++i ) {
103 CriKeyPoint *pt = that.points[i];
104 add_point(pt->tag, pt->e, pt->x, pt->y, pt->t);
108 void CriKeyConfig::interpolate(CriKeyConfig &prev, CriKeyConfig &next,
109 long prev_frame, long next_frame, long current_frame)
111 this->threshold = prev.threshold;
112 this->draw_mode = prev.draw_mode;
113 this->drag = prev.drag;
114 this->selected = prev.selected;
116 double next_scale = (double)(current_frame - prev_frame) / (next_frame - prev_frame);
117 double prev_scale = (double)(next_frame - current_frame) / (next_frame - prev_frame);
119 points.remove_all_objects();
120 int prev_sz = prev.points.size(), next_sz = next.points.size();
121 for( int i=0; i<prev_sz; ++i ) {
122 CriKeyPoint *pt = prev.points[i], *nt = 0;
123 float x = pt->x, y = pt->y, t = pt->t;
124 int k = next_sz; // associated by tag id in next
125 while( --k >= 0 && pt->tag != (nt=next.points[k])->tag );
127 x = x * prev_scale + nt->x * next_scale;
128 y = y * prev_scale + nt->y * next_scale;
129 t = t * prev_scale + nt->t * next_scale;
131 add_point(pt->tag, pt->e, x, y, t);
135 void CriKeyConfig::limits()
137 bclamp(threshold, 0.0f, 1.0f);
138 bclamp(draw_mode, 0, DRAW_MODES-1);
141 int CriKeyConfig::add_point(int tag, int e, float x, float y, float t)
143 int k = points.size();
146 for( int i=k; --i>=0; ) {
147 int n = points[i]->tag;
148 if( n >= tag ) tag = n + 1;
151 points.append(new CriKeyPoint(tag, e, x, y, t));
155 void CriKeyConfig::del_point(int i)
157 points.remove_object_number(i);
163 class segment { public: int y, lt, rt; };
164 ArrayList<segment> stack;
166 void push(int y, int lt, int rt) {
167 segment &seg = stack.append();
168 seg.y = y; seg.lt = lt; seg.rt = rt;
170 void pop(int &y, int <, int &rt) {
171 segment &seg = stack.last();
172 y = seg.y; lt = seg.lt; rt = seg.rt;
179 bool edge_pixel(int i) { return edg[i] > 0; }
182 void fill(int x, int y);
185 FillRegion(VFrame *edg, VFrame *msk);
188 FillRegion::FillRegion(VFrame *edg, VFrame *msk)
190 this->w = msk->get_w();
191 this->h = msk->get_h();
192 this->msk = (uint8_t*) msk->get_data();
193 this->edg = (float*) edg->get_data();
196 void FillRegion::fill(int x, int y)
201 void FillRegion::run()
203 while( stack.size() > 0 ) {
207 for( int x=ilt; x<=irt; ++x,++ofs ) {
208 if( !msk[ofs] ) continue;
210 if( edge_pixel(ofs) ) continue;
213 for( int i=lt; --i>=0; ) {
214 if( !msk[--lofs] ) break;
215 msk[lofs] = 0; lt = i;
216 if( edge_pixel(lofs) ) break;
219 for( int i=rt; ++i< w; ) {
220 if( !msk[++rofs] ) break;
221 msk[rofs] = 0; rt = i;
222 if( edge_pixel(rofs) ) break;
224 if( y+1 < h ) push(y+1, lt, rt);
225 if( y-1 >= 0 ) push(y-1, lt, rt);
231 CriKey::CriKey(PluginServer *server)
232 : PluginVClient(server)
246 int CriKey::set_target(float *color, int x, int y)
248 if( x < 0 || x >= w ) return 1;
249 if( y < 0 || y >= h ) return 1;
250 uint8_t **src_rows = src->get_rows();
252 float *fp = (float*)(src_rows[y] + x*bpp);
253 for( int i=0; i<comp; ++i,++fp ) color[i] = *fp;
256 uint8_t *sp = src_rows[y] + x*bpp;
257 for( int i=0; i<comp; ++i,++sp ) color[i] = *sp;
262 const char* CriKey::plugin_title() { return N_("CriKey"); }
263 int CriKey::is_realtime() { return 1; }
265 NEW_WINDOW_MACRO(CriKey, CriKeyWindow);
266 LOAD_CONFIGURATION_MACRO(CriKey, CriKeyConfig)
268 int CriKey::new_point()
270 EDLSession *session = get_edl()->session;
271 float x = !session ? 0.f : session->output_w / 2.f;
272 float y = !session ? 0.f : session->output_h / 2.f;
273 return config.add_point(-1, 0, x, y, 0.5f);
276 void CriKeyConfig::save_data(KeyFrame *keyframe)
280 // cause data to be stored directly in text
281 output.set_shared_output(keyframe->xbuf);
283 output.tag.set_title("CRIKEY");
284 output.tag.set_property("THRESHOLD", threshold);
285 output.tag.set_property("DRAW_MODE", draw_mode);
286 output.tag.set_property("DRAG", drag);
287 output.tag.set_property("SELECTED", selected);
289 output.append_newline();
290 output.tag.set_title("/CRIKEY");
292 output.append_newline();
293 for( int i=0, n = points.size(); i<n; ++i ) {
294 CriKeyPoint *pt = points[i];
295 char point[BCSTRLEN];
296 sprintf(point,"/POINT_%d",pt->tag);
297 output.tag.set_title(point+1);
298 output.tag.set_property("E", pt->e);
299 output.tag.set_property("X", pt->x);
300 output.tag.set_property("Y", pt->y);
301 output.tag.set_property("T", pt->t);
303 output.tag.set_title(point+0);
305 output.append_newline();
307 output.terminate_string();
310 void CriKey::save_data(KeyFrame *keyframe)
312 config.save_data(keyframe);
315 void CriKeyConfig::read_data(KeyFrame *keyframe)
318 input.set_shared_input(keyframe->xbuf);
319 points.remove_all_objects();
322 while( !(result=input.read_tag()) ) {
323 if( input.tag.title_is("CRIKEY") ) {
324 threshold = input.tag.get_property("THRESHOLD", threshold);
325 draw_mode = input.tag.get_property("DRAW_MODE", draw_mode);
326 drag = input.tag.get_property("DRAG", drag);
327 selected = input.tag.get_property("SELECTED", 0);
330 else if( !strncmp(input.tag.get_title(),"POINT_",6) ) {
331 int tag = atoi(input.tag.get_title() + 6);
332 int e = input.tag.get_property("E", 0);
333 float x = input.tag.get_property("X", 0.f);
334 float y = input.tag.get_property("Y", 0.f);
335 float t = input.tag.get_property("T", .5f);
336 add_point(tag, e, x, y, t);
341 void CriKey::read_data(KeyFrame *keyframe)
343 config.read_data(keyframe);
344 if( !config.points.size() ) new_point();
347 void CriKey::span_keyframes(KeyFrame *src, int64_t start, int64_t end)
349 CriKeyConfig src_config;
350 src_config.read_data(src);
351 KeyFrames *keyframes = (KeyFrames *)src->autos;
352 KeyFrame *prev = keyframes->get_prev_keyframe(start, PLAY_FORWARD);
353 CriKeyConfig prev_config;
354 prev_config.read_data(prev);
355 // Always update the first one
356 update_parameter(prev_config, src_config, prev);
357 KeyFrame *curr = (KeyFrame*)prev->next;
358 while( curr && curr->position < end ) {
359 update_parameter(prev_config, src_config, curr);
360 curr = (KeyFrame*)curr->next;
364 void CriKeyPoint::update_parameter(CriKeyPoint *prev, CriKeyPoint *src)
366 if( prev->e != src->e ) e = src->e;
367 if( prev->x != src->x ) x = src->x;
368 if( prev->y != src->y ) y = src->y;
369 if( prev->t != src->t ) t = src->t;
372 void CriKey::update_parameter(CriKeyConfig &prev_config, CriKeyConfig &src_config,
375 CriKeyConfig dst_config;
376 dst_config.read_data(keyframe);
377 if( !EQUIV(prev_config.threshold, src_config.threshold) )
378 dst_config.threshold = src_config.threshold;
379 if( prev_config.draw_mode != src_config.draw_mode )
380 dst_config.draw_mode = src_config.draw_mode;
381 if( prev_config.drag != src_config.drag )
382 dst_config.drag = src_config.drag;
383 int src_points = src_config.points.size();
384 int dst_points = dst_config.points.size();
385 int prev_points = prev_config.points.size();
386 int npoints = bmin(prev_points, bmin(src_points, dst_points));
387 for( int i=0; i<npoints; ++i ) {
388 CriKeyPoint *src_point = src_config.points[i];
389 int tag = src_point->tag, k = prev_points;
390 while( --k >= 0 && tag != prev_config.points[k]->tag );
391 if( k < 0 ) continue;
392 CriKeyPoint *prev_point = prev_config.points[k];
394 while( --k >= 0 && tag != dst_config.points[k]->tag );
395 if( k < 0 ) continue;
396 CriKeyPoint *dst_point = dst_config.points[k];
397 dst_point->update_parameter(prev_point, src_point);
399 dst_config.save_data(keyframe);
402 void CriKey::update_gui()
404 if( !thread ) return;
405 thread->window->lock_window("CriKey::update_gui");
406 CriKeyWindow *window = (CriKeyWindow*)thread->window;
407 if( load_configuration() ) {
408 window->update_gui();
411 thread->window->unlock_window();
414 void CriKey::draw_alpha(VFrame *msk)
416 uint8_t **src_rows = src->get_rows();
417 uint8_t **msk_rows = msk->get_rows();
418 switch( color_model ) {
420 for( int y=0; y<h; ++y ) {
421 uint8_t *sp = src_rows[y], *mp = msk_rows[y];
422 for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
424 float *px = (float *)sp;
425 px[0] = px[1] = px[2] = 0;
430 for( int y=0; y<h; ++y ) {
431 uint8_t *sp = src_rows[y], *mp = msk_rows[y];
432 for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
434 float *px = (float *)sp;
440 for( int y=0; y<h; ++y ) {
441 uint8_t *sp = src_rows[y], *mp = msk_rows[y];
442 for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
445 px[0] = px[1] = px[2] = 0;
450 for( int y=0; y<h; ++y ) {
451 uint8_t *sp = src_rows[y], *mp = msk_rows[y];
452 for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
456 px[1] = px[2] = 0x80;
462 for( int y=0; y<h; ++y ) {
463 uint8_t *sp = src_rows[y], *mp = msk_rows[y];
464 for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
474 void CriKey::draw_edge(VFrame *edg)
476 uint8_t **src_rows = src->get_rows();
477 float **edg_rows = (float**) edg->get_rows(), gain = 10;
478 switch( color_model ) {
480 for( int y=0; y<h; ++y ) {
481 uint8_t *sp = src_rows[y];
482 float *ap = edg_rows[y];
483 for( int x=0; x<w; ++x,++ap,sp+=bpp ) {
484 float *px = (float *)sp, v = *ap * gain;
485 px[0] = px[1] = px[2] = v<1 ? v : 1;
490 for( int y=0; y<h; ++y ) {
491 uint8_t *sp = src_rows[y];
492 float *ap = edg_rows[y];
493 for( int x=0; x<w; ++x,++ap,sp+=bpp ) {
494 float *px = (float *)sp, v = *ap * gain;
495 px[0] = px[1] = px[2] = v<1 ? v : 1;
501 for( int y=0; y<h; ++y ) {
502 uint8_t *sp = src_rows[y];
503 float *ap = edg_rows[y];
504 for( int x=0; x<w; ++x,++ap,sp+=bpp ) {
505 uint8_t *px = sp; float v = *ap * gain;
506 px[0] = px[1] = px[2] = v<1 ? v*256 : 255;
511 for( int y=0; y<h; ++y ) {
512 uint8_t *sp = src_rows[y];
513 float *ap = edg_rows[y];
514 for( int x=0; x<w; ++x,++ap,sp+=bpp ) {
515 uint8_t *px = sp; float v = *ap * gain;
516 px[0] = px[1] = px[2] = v<1 ? v*256 : 255;
522 for( int y=0; y<h; ++y ) {
523 uint8_t *sp = src_rows[y];
524 float *ap = edg_rows[y];
525 for( int x=0; x<w; ++x,++ap,sp+=bpp ) {
526 uint8_t *px = sp; float v = *ap * gain;
527 px[0] = *ap<1 ? v*256 : 255;
528 px[1] = px[2] = 0x80;
533 for( int y=0; y<h; ++y ) {
534 uint8_t *sp = src_rows[y];
535 float *ap = edg_rows[y];
536 for( int x=0; x<w; ++x,++ap,sp+=bpp ) {
537 uint8_t *px = sp; float v = *ap * gain;
538 px[0] = *ap<1 ? v*256 : 255;
539 px[1] = px[2] = 0x80;
547 void CriKey::draw_mask(VFrame *msk)
549 uint8_t **src_rows = src->get_rows();
550 uint8_t **msk_rows = msk->get_rows();
551 float scale = 1 / 255.0f;
552 switch( color_model ) {
554 for( int y=0; y<h; ++y ) {
555 uint8_t *sp = src_rows[y], *mp = msk_rows[y];
556 for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
557 float a = *mp * scale;
558 float *px = (float *)sp;
559 px[0] = px[1] = px[2] = a;
564 for( int y=0; y<h; ++y ) {
565 uint8_t *sp = src_rows[y], *mp = msk_rows[y];
566 for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
567 float a = *mp * scale;
568 float *px = (float *)sp;
569 px[0] = px[1] = px[2] = a;
575 for( int y=0; y<h; ++y ) {
576 uint8_t *sp = src_rows[y], *mp = msk_rows[y];
577 for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
578 float a = *mp * scale;
580 px[0] = px[1] = px[2] = a * 255;
585 for( int y=0; y<h; ++y ) {
586 uint8_t *sp = src_rows[y], *mp = msk_rows[y];
587 for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
588 float a = *mp * scale;
590 px[0] = px[1] = px[2] = a * 255;
596 for( int y=0; y<h; ++y ) {
597 uint8_t *sp = src_rows[y], *mp = msk_rows[y];
598 for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
599 float a = *mp * scale;
602 px[1] = px[2] = 0x80;
607 for( int y=0; y<h; ++y ) {
608 uint8_t *sp = src_rows[y], *mp = msk_rows[y];
609 for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
610 float a = *mp * scale;
613 px[1] = px[2] = 0x80;
622 void CriKey::draw_point(VFrame *src, CriKeyPoint *pt)
624 int d = bmax(w,h) / 200 + 2;
625 int r = d/2+1, x = pt->x, y = pt->y;
626 src->draw_smooth(x-r,y+0, x-r, y-r, x+0,y-r);
627 src->draw_smooth(x+0,y-r, x+r, y-r, x+r,y+0);
628 src->draw_smooth(x+r,y+0, x+r, y+r, x+0,y+r);
629 src->draw_smooth(x+0,y+r, x-r, y+r, x-r,y+0);
631 src->set_pixel_color(RED);
632 src->draw_x(pt->x, pt->y, d);
635 src->set_pixel_color(BLUE);
636 src->draw_t(pt->x, pt->y, d);
641 static void fill_edge(VFrame *vfrm, int w, int h)
643 int w1 = w-1, h1 = h-1;
644 float *dp = (float*) vfrm->get_data();
645 if( w1 > 0 ) for( int y=0; y<h1; ++y,dp+=w ) dp[w1] = dp[w1-1];
646 if( h1 > 0 ) for( int x=0; x<w; ++x ) dp[x] = dp[x-w];
649 int CriKey::process_buffer(VFrame *frame, int64_t start_position, double frame_rate)
651 load_configuration();
653 w = src->get_w(), h = src->get_h();
654 color_model = src->get_color_model();
655 bpp = BC_CModels::calculate_pixelsize(color_model);
656 is_float = BC_CModels::is_float(color_model);
657 is_yuv = BC_CModels::is_yuv(color_model);
658 comp = BC_CModels::components(color_model);
659 if( comp > 3 ) comp = 3;
661 read_frame(src, 0, start_position, frame_rate, 0);
662 VFrame::get_temp(edg, w, h, BC_A_FLOAT);
665 engine = new CriKeyEngine(this,
666 PluginClient::get_project_smp() + 1,
667 PluginClient::get_project_smp() + 1);
669 VFrame::get_temp(msk, w, h, BC_A8);
670 memset(msk->get_data(), 0xff, msk->get_data_size());
672 for( int i=0, n=config.points.size(); i<n; ++i ) {
673 CriKeyPoint *pt = config.points[i];
674 if( !pt->e ) continue;
675 if( set_target(engine->color, pt->x, pt->y) ) continue;
676 engine->threshold = pt->t;
678 engine->process_packages();
679 fill_edge(edg, w, h);
680 FillRegion fill_region(edg, msk);
681 fill_region.fill(pt->x, pt->y);
685 //crikey_pgm("/tmp/msk.pgm",msk);
686 switch( config.draw_mode ) {
687 case DRAW_ALPHA: draw_alpha(msk); break;
688 case DRAW_EDGE: draw_edge(edg); break;
689 case DRAW_MASK: draw_mask(msk); break;
693 for( int i=0, n=config.points.size(); i<n; ++i ) {
694 CriKeyPoint *pt = config.points[i];
695 src->set_pixel_color(config.selected == i ? GREEN : WHITE);
703 void CriKeyEngine::init_packages()
705 int y = 0, h1 = plugin->h-1;
706 for(int i = 0; i < get_total_packages(); ) {
707 CriKeyPackage *pkg = (CriKeyPackage*)get_package(i++);
709 y = h1 * i / LoadServer::get_total_packages();
714 LoadPackage* CriKeyEngine::new_package()
716 return new CriKeyPackage();
719 LoadClient* CriKeyEngine::new_client()
721 return new CriKeyUnit(this);
724 #define EDGE_MACRO(type, components, is_yuv) \
726 uint8_t **src_rows = src->get_rows(); \
727 int comps = MIN(components, 3); \
728 for( int y=y1; y<y2; ++y ) { \
729 uint8_t *row0 = src_rows[y], *row1 = src_rows[y+1]; \
730 float *edgp = edg_rows[y]; \
731 for( int x=x1; x<x2; ++edgp,++x,row0+=bpp,row1+=bpp ) { \
732 type *r0 = (type*)row0, *r1 = (type*)row1; \
733 float a00 = 0, a01 = 0, a10 = 0, a11 = 0; \
734 for( int c=0; c<comps; ++c,++r0,++r1 ) { \
735 float t = target_color[c]; \
736 a00 += fabs(t - r0[0]); \
737 a01 += fabs(t - r0[components]); \
738 a10 += fabs(t - r1[0]); \
739 a11 += fabs(t - r1[components]); \
741 float mx = scale * bmax(bmax(a00, a01), bmax(a10, a11)); \
742 if( mx < threshold ) continue; \
743 float mn = scale * bmin(bmin(a00, a01), bmin(a10, a11)); \
744 if( mn >= threshold ) continue; \
745 *edgp += (mx - mn); \
751 void CriKeyUnit::process_package(LoadPackage *package)
753 int color_model = server->plugin->color_model;
754 int bpp = server->plugin->bpp;
755 VFrame *src = server->plugin->src;
756 VFrame *edg = server->plugin->edg;
757 float **edg_rows = (float**)edg->get_rows();
758 float *target_color = server->color;
759 float threshold = 2.f * server->threshold*server->threshold;
760 float scale = 1./BC_CModels::calculate_max(color_model);
761 CriKeyPackage *pkg = (CriKeyPackage*)package;
762 int x1 = 0, x2 = server->plugin->w-1;
763 int y1 = pkg->y1, y2 = pkg->y2;
765 switch( color_model ) {
766 case BC_RGB_FLOAT: EDGE_MACRO(float, 3, 0);
767 case BC_RGBA_FLOAT: EDGE_MACRO(float, 4, 0);
768 case BC_RGB888: EDGE_MACRO(unsigned char, 3, 0);
769 case BC_YUV888: EDGE_MACRO(unsigned char, 3, 1);
770 case BC_RGBA8888: EDGE_MACRO(unsigned char, 4, 0);
771 case BC_YUVA8888: EDGE_MACRO(unsigned char, 4, 1);