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"
32 #include "crikeywindow.h"
36 // chroma interpolated key, crikey
38 REGISTER_PLUGIN(CriKey)
41 void crikey_pgm(const char *fn,VFrame *vfrm)
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);
51 CriKeyConfig::CriKeyConfig()
55 draw_mode = DRAW_ALPHA;
56 key_mode = KEY_SEARCH;
57 point_x = point_y = 0;
61 int CriKeyConfig::equivalent(CriKeyConfig &that)
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;
72 void CriKeyConfig::copy_from(CriKeyConfig &that)
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;
83 void CriKeyConfig::interpolate(CriKeyConfig &prev, CriKeyConfig &next,
84 long prev_frame, long next_frame, long current_frame)
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 ) {
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;
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);
107 void CriKeyConfig::limits()
109 bclamp(threshold, 0.0f, 1.0f);
110 bclamp(draw_mode, 0, DRAW_MODES-1);
111 bclamp(key_mode, 0, KEY_MODES-1);
117 class segment { public: int y, lt, rt; };
118 ArrayList<segment> stack;
120 void push(int y, int lt, int rt) {
121 segment &seg = stack.append();
122 seg.y = y; seg.lt = lt; seg.rt = rt;
124 void pop(int &y, int <, int &rt) {
125 segment &seg = stack.last();
126 y = seg.y; lt = seg.lt; rt = seg.rt;
131 uint8_t *data, *mask;
132 bool edge_pixel(uint8_t *dp) { return *dp; }
135 void fill(int x, int y);
136 void set_threshold(float v) { threshold = v; }
138 FillRegion(VFrame *d, VFrame *m);
141 FillRegion::FillRegion(VFrame *d, VFrame *m)
146 data = d->get_data();
147 mask = m->get_data();
150 void FillRegion::fill(int x, int y)
155 int ilt, irt, lt, rt;
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;
165 for( int i=lt; --i>=0; lt=i,*mp=0 )
166 if( !*--mp || edge_pixel(--dp) ) break;
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);
173 } while( stack.size() > 0 );
177 CriKey::CriKey(PluginServer *server)
178 : PluginVClient(server)
193 void CriKeyConfig::set_target(int is_yuv, int color, float *target)
195 float r = ((color>>16) & 0xff) / 255.0f;
196 float g = ((color>> 8) & 0xff) / 255.0f;
197 float b = ((color>> 0) & 0xff) / 255.0f;
200 YUV::rgb_to_yuv_f(r,g,b, y,u,v);
202 target[1] = u + 0.5f;
203 target[2] = v + 0.5f;
211 void CriKeyConfig::set_color(int is_yuv, float *target, int &color)
217 float y = r, u = g-0.5f, v = b-0.5f;
218 YUV::yuv_to_rgb_f(y,u,v, r,g,b);
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);
226 void CriKey::get_color(int x, int y)
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;
233 float *fp = (float *)sp;
234 for( int i=0; i<comp; ++i,++fp )
238 float scale = 1./255;
239 for( int i=0; i<comp; ++i,++sp )
240 target[i] = *sp * scale;
244 float CriKey::diff_uint8(uint8_t *dp)
246 float scale = 1./255., v = 0;
247 for( int i=0; i<comp; ++i,++dp )
248 v += fabs(*dp * scale - target[i]);
251 float CriKey::diff_float(uint8_t *dp)
253 float *fp = (float *)dp, v = 0;
254 for( int i=0; i<comp; ++i,++fp )
255 v += fabs(*fp - target[i]);
259 void CriKey::min_key(int &ix, int &iy)
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;
272 bool CriKey::find_key(int &ix, int &iy, float thr)
274 uint8_t **src_rows = src->get_rows();
275 uint8_t **msk_rows = msk->get_rows();
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 ) {
282 float v = (this->*diff_pixel)(sp);
293 const char* CriKey::plugin_title() { return _("CriKey"); }
294 int CriKey::is_realtime() { return 1; }
296 NEW_WINDOW_MACRO(CriKey, CriKeyWindow);
297 LOAD_CONFIGURATION_MACRO(CriKey, CriKeyConfig)
299 void CriKey::save_data(KeyFrame *keyframe)
303 // cause data to be stored directly in text
304 output.set_shared_output(keyframe->get_data(), MESSAGESIZE);
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);
315 output.append_newline();
316 output.tag.set_title("/CRIKEY");
318 output.append_newline();
319 output.terminate_string();
322 void CriKey::read_data(KeyFrame *keyframe)
325 input.set_shared_input(keyframe->get_data(), strlen(keyframe->get_data()));
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);
339 else if(input.tag.title_is("/CRIKEY")) {
346 void CriKey::update_gui()
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();
354 thread->window->unlock_window();
357 void CriKey::draw_alpha(VFrame *msk)
359 uint8_t **src_rows = src->get_rows();
360 uint8_t **msk_rows = msk->get_rows();
361 switch( color_model ) {
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 ) {
367 float *px = (float *)sp;
368 px[0] = px[1] = px[2] = 0;
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 ) {
377 float *px = (float *)sp;
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 ) {
388 px[0] = px[1] = px[2] = 0;
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 ) {
399 px[1] = px[2] = 0x80;
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 ) {
417 void CriKey::draw_mask(VFrame *msk)
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 ) {
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;
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;
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;
450 px[0] = px[1] = px[2] = a * 255;
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;
460 px[0] = px[1] = px[2] = a * 255;
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;
472 px[1] = px[2] = 0x80;
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;
483 px[1] = px[2] = 0x80;
491 int CriKey::process_buffer(VFrame *frame, int64_t start_position, double frame_rate)
493 load_configuration();
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;
504 read_frame(src, 0, start_position, frame_rate, 0);
506 switch( config.key_mode ) {
509 set_target(is_yuv, config.color, target);
512 get_color(config.point_x, config.point_y);
516 if( dst && ( dst->get_w() != w || src->get_h() != h ) ) {
520 dst = new VFrame(w, h, BC_A8);
521 memset(dst->get_data(), 0x00, dst->get_data_size());
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);
535 if( config.draw_mode == DRAW_EDGE ) {
540 if( msk && ( msk->get_w() != w || msk->get_h() != h ) ) {
544 msk = new VFrame(w, h, BC_A8);
545 memset(msk->get_data(), 0xff, msk->get_data_size());
547 FillRegion fill_region(dst, msk);
548 fill_region.set_threshold(config.threshold);
551 switch( config.key_mode ) {
554 fill_region.fill(x, y);
557 while( find_key(x, y, config.threshold) ) {
558 fill_region.fill(x, y);
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);
568 //crikey_pgm("/tmp/msk.pgm",msk);
570 if( config.draw_mode == DRAW_MASK ) {
580 void CriKeyEngine::init_packages()
582 int y = 0, h1 = plugin->h-1;
583 for(int i = 0; i < get_total_packages(); ) {
584 CriKeyPackage *pkg = (CriKeyPackage*)get_package(i++);
586 y = h1 * i / LoadServer::get_total_packages();
591 LoadPackage* CriKeyEngine::new_package()
593 return new CriKeyPackage();
596 LoadClient* CriKeyEngine::new_client()
598 return new CriKeyUnit(this);
601 #define EDGE_MACRO(type, max, components, is_yuv) \
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); \
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; \
630 void CriKeyUnit::process_package(LoadPackage *package)
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;
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);