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 t, int e, float x, float y)
54 this->t = t; this->e = e; this->x = x; this->y = y;
56 CriKeyPoint::~CriKeyPoint()
60 CriKeyConfig::CriKeyConfig()
64 draw_mode = DRAW_ALPHA;
68 CriKeyConfig::~CriKeyConfig()
70 points.remove_all_objects();
73 int CriKeyConfig::equivalent(CriKeyConfig &that)
75 if( this->color != that.color ) return 0;
76 if( !EQUIV(this->threshold, that.threshold) ) return 0;
77 if( this->draw_mode != that.draw_mode ) return 0;
78 if( this->drag != that.drag ) return 0;
79 if( this->points.size() != that.points.size() ) return 0;
80 for( int i=0, n=points.size(); i<n; ++i ) {
81 CriKeyPoint *ap = this->points[i], *bp = that.points[i];
82 if( ap->t != bp->t ) return 0;
83 if( ap->e != bp->e ) return 0;
84 if( !EQUIV(ap->x, bp->x) ) return 0;
85 if( !EQUIV(ap->y, bp->y) ) return 0;
90 void CriKeyConfig::copy_from(CriKeyConfig &that)
92 this->color = that.color;
93 this->threshold = that.threshold;
94 this->draw_mode = that.draw_mode;
95 this->drag = that.drag;
96 this->selected = that.selected;
98 points.remove_all_objects();
99 for( int i=0,n=that.points.size(); i<n; ++i ) {
100 CriKeyPoint *pt = that.points[i];
101 add_point(pt->t, pt->e, pt->x, pt->y);
105 void CriKeyConfig::interpolate(CriKeyConfig &prev, CriKeyConfig &next,
106 long prev_frame, long next_frame, long current_frame)
108 this->color = prev.color;
109 this->draw_mode = prev.draw_mode;
110 this->drag = prev.drag;
111 this->selected = prev.selected;
113 double next_scale = (double)(current_frame - prev_frame) / (next_frame - prev_frame);
114 double prev_scale = (double)(next_frame - current_frame) / (next_frame - prev_frame);
115 this->threshold = prev.threshold * prev_scale + next.threshold * next_scale;
116 // interpolate rgb components
117 float prev_target[3]; set_target(0, prev.color, prev_target);
118 float next_target[3]; set_target(0, next.color, next_target);
120 for( int i=0; i<3; ++i )
121 target[i] = prev_target[i] * prev_scale + next_target[i] * next_scale;
122 set_color(0, target, this->color);
124 points.remove_all_objects();
125 int prev_sz = prev.points.size(), next_sz = next.points.size();
126 for( int i=0; i<prev_sz; ++i ) {
127 CriKeyPoint *pt = prev.points[i], *nt = 0;
128 float x = pt->x, y = pt->y;
129 int k = next_sz; // associated by tag id in next
130 while( --k >= 0 && pt->t != (nt=next.points[k])->t );
132 x = x * prev_scale + nt->x * next_scale;
133 y = y * prev_scale + nt->y * next_scale;
135 add_point(pt->t, pt->e, x, y);
139 void CriKeyConfig::limits()
141 bclamp(threshold, 0.0f, 1.0f);
142 bclamp(draw_mode, 0, DRAW_MODES-1);
145 int CriKeyConfig::add_point(int t, int e, float x, float y)
147 int k = points.size();
150 for( int i=k; --i>=0; ) {
151 int n = points[i]->t;
152 if( n >= t ) t = n + 1;
155 points.append(new CriKeyPoint(t, e, x, y));
159 void CriKeyConfig::del_point(int i)
161 points.remove_object_number(i);
167 class segment { public: int y, lt, rt; };
168 ArrayList<segment> stack;
170 void push(int y, int lt, int rt) {
171 segment &seg = stack.append();
172 seg.y = y; seg.lt = lt; seg.rt = rt;
174 void pop(int &y, int <, int &rt) {
175 segment &seg = stack.last();
176 y = seg.y; lt = seg.lt; rt = seg.rt;
181 uint8_t *data, *mask;
182 bool edge_pixel(uint8_t *dp) { return *dp; }
185 void fill(int x, int y);
188 FillRegion(VFrame *d, VFrame *m);
191 FillRegion::FillRegion(VFrame *d, VFrame *m)
195 data = d->get_data();
196 mask = m->get_data();
199 void FillRegion::fill(int x, int y)
204 void FillRegion::run()
206 while( stack.size() > 0 ) {
210 uint8_t *idp = data + ofs;
211 uint8_t *imp = mask + ofs;
212 for( int x=ilt; x<=irt; ++x,++imp,++idp ) {
213 if( !*imp ) continue;
215 if( edge_pixel(idp) ) continue;
217 uint8_t *ldp = idp, *lmp = imp;
218 for( int i=lt; --i>=0; ) {
221 if( edge_pixel(--ldp) ) break;
223 uint8_t *rdp = idp, *rmp = imp;
224 for( int i=rt; ++i< w; rt=i,*rmp=0 ) {
227 if( edge_pixel(++rdp) ) break;
229 if( y+1 < h ) push(y+1, lt, rt);
230 if( y-1 >= 0 ) push(y-1, lt, rt);
236 CriKey::CriKey(PluginServer *server)
237 : PluginVClient(server)
251 void CriKeyConfig::set_target(int is_yuv, int color, float *target)
253 float r = ((color>>16) & 0xff) / 255.0f;
254 float g = ((color>> 8) & 0xff) / 255.0f;
255 float b = ((color>> 0) & 0xff) / 255.0f;
258 YUV::yuv.rgb_to_yuv_f(r,g,b, y,u,v);
260 target[1] = u + 0.5f;
261 target[2] = v + 0.5f;
269 void CriKeyConfig::set_color(int is_yuv, float *target, int &color)
275 float y = r, u = g-0.5f, v = b-0.5f;
276 YUV::yuv.yuv_to_rgb_f(y,u,v, r,g,b);
278 int ir = r >= 1 ? 0xff : r < 0 ? 0 : (int)(r * 256);
279 int ig = g >= 1 ? 0xff : g < 0 ? 0 : (int)(g * 256);
280 int ib = b >= 1 ? 0xff : b < 0 ? 0 : (int)(b * 256);
281 color = (ir << 16) | (ig << 8) | (ib << 0);
284 void CriKey::get_color(int x, int y)
286 if( x < 0 || x >= w ) return;
287 if( y < 0 || y >= h ) return;
288 uint8_t **src_rows = src->get_rows();
289 uint8_t *sp = src_rows[y] + x*bpp;
291 float *fp = (float *)sp;
292 for( int i=0; i<comp; ++i,++fp )
296 float scale = 1./255;
297 for( int i=0; i<comp; ++i,++sp )
298 target[i] = *sp * scale;
302 const char* CriKey::plugin_title() { return N_("CriKey"); }
303 int CriKey::is_realtime() { return 1; }
305 NEW_WINDOW_MACRO(CriKey, CriKeyWindow);
306 LOAD_CONFIGURATION_MACRO(CriKey, CriKeyConfig)
308 int CriKey::new_point()
310 EDLSession *session = get_edlsession();
311 float x = !session ? 0.f : session->output_w / 2.f;
312 float y = !session ? 0.f : session->output_h / 2.f;
313 return config.add_point(-1, 0, x, y);
316 void CriKey::save_data(KeyFrame *keyframe)
320 // cause data to be stored directly in text
321 output.set_shared_output(keyframe->get_data(), MESSAGESIZE);
323 output.tag.set_title("CRIKEY");
324 output.tag.set_property("COLOR", config.color);
325 output.tag.set_property("THRESHOLD", config.threshold);
326 output.tag.set_property("DRAW_MODE", config.draw_mode);
327 output.tag.set_property("DRAG", config.drag);
328 output.tag.set_property("SELECTED", config.selected);
330 output.append_newline();
331 output.tag.set_title("/CRIKEY");
333 output.append_newline();
334 for( int i=0, n = config.points.size(); i<n; ++i ) {
335 CriKeyPoint *pt = config.points[i];
336 char point[BCSTRLEN];
337 sprintf(point,"/POINT_%d",pt->t);
338 output.tag.set_title(point+1);
339 output.tag.set_property("E", pt->e);
340 output.tag.set_property("X", pt->x);
341 output.tag.set_property("Y", pt->y);
343 output.tag.set_title(point+0);
345 output.append_newline();
347 output.terminate_string();
350 void CriKey::read_data(KeyFrame *keyframe)
353 input.set_shared_input(keyframe->get_data(), strlen(keyframe->get_data()));
354 config.points.remove_all_objects();
357 while( !(result=input.read_tag()) ) {
358 if( input.tag.title_is("CRIKEY") ) {
359 config.color = input.tag.get_property("COLOR", config.color);
360 config.threshold = input.tag.get_property("THRESHOLD", config.threshold);
361 config.draw_mode = input.tag.get_property("DRAW_MODE", config.draw_mode);
362 config.drag = input.tag.get_property("DRAG", config.drag);
363 config.selected = input.tag.get_property("SELECTED", 0);
366 else if( !strncmp(input.tag.get_title(),"POINT_",6) ) {
367 int t = atoi(input.tag.get_title() + 6);
368 int e = input.tag.get_property("E", 0);
369 float x = input.tag.get_property("X", 0.f);
370 float y = input.tag.get_property("Y", 0.f);
371 config.add_point(t, e, x, y);
375 if( !config.points.size() ) new_point();
378 void CriKey::update_gui()
380 if( !thread ) return;
381 if( !load_configuration() ) return;
382 thread->window->lock_window("CriKey::update_gui");
383 CriKeyWindow *window = (CriKeyWindow*)thread->window;
384 window->update_gui();
386 thread->window->unlock_window();
389 void CriKey::draw_alpha(VFrame *msk)
391 uint8_t **src_rows = src->get_rows();
392 uint8_t **msk_rows = msk->get_rows();
393 switch( color_model ) {
395 for( int y=0; y<h; ++y ) {
396 uint8_t *sp = src_rows[y], *mp = msk_rows[y];
397 for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
399 float *px = (float *)sp;
400 px[0] = px[1] = px[2] = 0;
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 ) {
409 float *px = (float *)sp;
415 for( int y=0; y<h; ++y ) {
416 uint8_t *sp = src_rows[y], *mp = msk_rows[y];
417 for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
420 px[0] = px[1] = px[2] = 0;
425 for( int y=0; y<h; ++y ) {
426 uint8_t *sp = src_rows[y], *mp = msk_rows[y];
427 for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
431 px[1] = px[2] = 0x80;
437 for( int y=0; y<h; ++y ) {
438 uint8_t *sp = src_rows[y], *mp = msk_rows[y];
439 for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
449 void CriKey::draw_mask(VFrame *msk)
451 uint8_t **src_rows = src->get_rows();
452 uint8_t **msk_rows = msk->get_rows();
453 float scale = 1 / 255.0f;
454 switch( color_model ) {
456 for( int y=0; y<h; ++y ) {
457 uint8_t *sp = src_rows[y], *mp = msk_rows[y];
458 for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
459 float a = *mp * scale;
460 float *px = (float *)sp;
461 px[0] = px[1] = px[2] = a;
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 float *px = (float *)sp;
471 px[0] = px[1] = px[2] = a;
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;
482 px[0] = px[1] = px[2] = a * 255;
487 for( int y=0; y<h; ++y ) {
488 uint8_t *sp = src_rows[y], *mp = msk_rows[y];
489 for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
490 float a = *mp * scale;
492 px[0] = px[1] = px[2] = a * 255;
498 for( int y=0; y<h; ++y ) {
499 uint8_t *sp = src_rows[y], *mp = msk_rows[y];
500 for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
501 float a = *mp * scale;
504 px[1] = px[2] = 0x80;
509 for( int y=0; y<h; ++y ) {
510 uint8_t *sp = src_rows[y], *mp = msk_rows[y];
511 for( int x=0; x<w; ++x,++mp,sp+=bpp ) {
512 float a = *mp * scale;
515 px[1] = px[2] = 0x80;
523 void CriKey::draw_point(VFrame *src, CriKeyPoint *pt)
525 int d = bmax(w,h) / 200 + 2;
526 int r = d/2+1, x = pt->x, y = pt->y;
527 src->draw_smooth(x-r,y+0, x-r, y-r, x+0,y-r);
528 src->draw_smooth(x+0,y-r, x+r, y-r, x+r,y+0);
529 src->draw_smooth(x+r,y+0, x+r, y+r, x+0,y+r);
530 src->draw_smooth(x+0,y+r, x-r, y+r, x-r,y+0);
532 src->set_pixel_color(RED);
533 src->draw_x(pt->x, pt->y, d);
536 src->set_pixel_color(BLUE);
537 src->draw_t(pt->x, pt->y, d);
541 int CriKey::process_buffer(VFrame *frame, int64_t start_position, double frame_rate)
543 load_configuration();
545 w = src->get_w(), h = src->get_h();
546 color_model = src->get_color_model();
547 bpp = BC_CModels::calculate_pixelsize(color_model);
548 comp = BC_CModels::components(color_model);
549 if( comp > 3 ) comp = 3;
550 is_yuv = BC_CModels::is_yuv(color_model);
551 is_float = BC_CModels::is_float(color_model);
553 read_frame(src, 0, start_position, frame_rate, 0);
555 set_target(is_yuv, config.color, target);
557 if( dst && ( dst->get_w() != w || src->get_h() != h ) ) {
561 dst = new VFrame(w, h, BC_A8, 0);
565 engine = new CriKeyEngine(this,
566 PluginClient::get_project_smp() + 1,
567 PluginClient::get_project_smp() + 1);
568 engine->process_packages();
569 // copy fill btm/rt edges
570 int w1 = w-1, h1 = h-1;
571 uint8_t *dp = dst->get_data();
572 if( w1 > 0 ) for( int y=0; y<h1; ++y,dp+=w ) dp[w1] = dp[w1-1];
573 if( h1 > 0 ) for( int x=0; x<w; ++x ) dp[x] = dp[x-w];
574 //crikey_pgm("/tmp/dst.pgm",dst);
576 if( config.draw_mode != DRAW_EDGE ) {
577 if( msk && ( msk->get_w() != w || msk->get_h() != h ) ) {
581 msk = new VFrame(w, h, BC_A8, 0);
582 memset(msk->get_data(), 0xff, msk->get_data_size());
584 FillRegion fill_region(dst, msk);
585 for( int i=0, n=config.points.size(); i<n; ++i ) {
586 CriKeyPoint *pt = config.points[i];
587 if( !pt->e ) continue;
588 float x = pt->x, y = pt->y;
589 if( x >= 0 && x < w && y >= 0 && y < h )
590 fill_region.fill(x, y);
594 //crikey_pgm("/tmp/msk.pgm",msk);
596 if( config.draw_mode == DRAW_MASK )
605 for( int i=0, n=config.points.size(); i<n; ++i ) {
606 CriKeyPoint *pt = config.points[i];
607 src->set_pixel_color(config.selected == i ? GREEN : WHITE);
615 void CriKeyEngine::init_packages()
617 int y = 0, h1 = plugin->h-1;
618 for(int i = 0; i < get_total_packages(); ) {
619 CriKeyPackage *pkg = (CriKeyPackage*)get_package(i++);
621 y = h1 * i / LoadServer::get_total_packages();
626 LoadPackage* CriKeyEngine::new_package()
628 return new CriKeyPackage();
631 LoadClient* CriKeyEngine::new_client()
633 return new CriKeyUnit(this);
636 #define EDGE_MACRO(type, max, components, is_yuv) \
638 uint8_t **src_rows = src->get_rows(); \
639 int comps = MIN(components, 3); \
640 float scale = 1.0f/max; \
641 for( int y=y1; y<y2; ++y ) { \
642 uint8_t *row0 = src_rows[y], *row1 = src_rows[y+1]; \
643 uint8_t *outp = dst_rows[y]; \
644 for( int v,x=x1; x<x2; *outp++=v,++x,row0+=bpp,row1+=bpp ) { \
645 type *r0 = (type*)row0, *r1 = (type*)row1; \
646 float a00 = 0, a01 = 0, a10 = 0, a11 = 0; \
647 for( int i=0; i<comps; ++i,++r0,++r1 ) { \
648 float t = target[i]; \
649 a00 += fabs(t - r0[0]*scale); \
650 a01 += fabs(t - r0[components]*scale); \
651 a10 += fabs(t - r1[0]*scale); \
652 a11 += fabs(t - r1[components]*scale); \
655 float a = bmin(bmin(a00, a01), bmin(a10, a11)); \
656 if( a > threshold ) continue; \
657 float b = bmax(bmax(a00, a01), bmax(a10, a11)); \
658 if( threshold >= b ) continue; \
665 void CriKeyUnit::process_package(LoadPackage *package)
667 VFrame *src = server->plugin->src;
668 int bpp = server->plugin->bpp;
669 VFrame *dst = server->plugin->dst;
670 uint8_t **dst_rows = dst->get_rows();
671 float *target = server->plugin->target;
672 float threshold = server->plugin->config.threshold;
673 CriKeyPackage *pkg = (CriKeyPackage*)package;
674 int x1 = 0, x2 = server->plugin->w-1;
675 int y1 = pkg->y1, y2 = pkg->y2;
677 switch( src->get_color_model() ) {
678 case BC_RGB_FLOAT: EDGE_MACRO(float, 1, 3, 0);
679 case BC_RGBA_FLOAT: EDGE_MACRO(float, 1, 4, 0);
680 case BC_RGB888: EDGE_MACRO(unsigned char, 0xff, 3, 0);
681 case BC_YUV888: EDGE_MACRO(unsigned char, 0xff, 3, 1);
682 case BC_RGBA8888: EDGE_MACRO(unsigned char, 0xff, 4, 0);
683 case BC_YUVA8888: EDGE_MACRO(unsigned char, 0xff, 4, 1);