4 * Copyright (C) 2008 Adam Williams <broadcast at earthling dot net>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 #include "bcdisplayinfo.h"
26 #include "filesystem.h"
29 #include "overlayframe.h"
32 #include "shapewipe.h"
40 #define SHAPE_SEARCHPATH "/shapes"
41 #define DEFAULT_SHAPE "circle"
42 // feather slider range log2 = -10 .. -1 == 9.8e-4 .. 0.5
43 #define SHAPE_FLOG_MIN -10.
44 #define SHAPE_FLOG_MAX -1.
45 #define SHAPE_FMIN expf(M_LN2*SHAPE_FLOG_MIN)
46 #define SHAPE_FMAX expf(M_LN2*SHAPE_FLOG_MAX)
48 REGISTER_PLUGIN(ShapeWipeMain)
50 ShapeWipeConfig::ShapeWipeConfig()
55 strcpy(shape_name, DEFAULT_SHAPE);
57 ShapeWipeConfig::~ShapeWipeConfig()
61 void ShapeWipeConfig::read_xml(KeyFrame *keyframe)
64 input.set_shared_input(keyframe->xbuf);
66 while( !input.read_tag() ) {
67 if( input.tag.title_is("SHAPEWIPE") ) {
68 direction = input.tag.get_property("DIRECTION", direction);
69 feather = input.tag.get_property("FEATHER", feather);
70 preserve_aspect = input.tag.get_property("PRESERVE_ASPECT", preserve_aspect);
71 input.tag.get_property("SHAPE_NAME", shape_name);
75 void ShapeWipeConfig::save_xml(KeyFrame *keyframe)
78 output.set_shared_output(keyframe->xbuf);
79 output.tag.set_title("SHAPEWIPE");
80 output.tag.set_property("DIRECTION", direction);
81 output.tag.set_property("FEATHER", feather);
82 output.tag.set_property("PRESERVE_ASPECT", preserve_aspect);
83 output.tag.set_property("SHAPE_NAME", shape_name);
85 output.tag.set_title("/SHAPEWIPE");
87 output.terminate_string();
91 ShapeWipeW2B::ShapeWipeW2B(ShapeWipeMain *plugin,
92 ShapeWipeWindow *window, int x, int y)
93 : BC_Radial(x, y, plugin->config.direction == 0, _("White to Black"))
95 this->plugin = plugin;
96 this->window = window;
99 int ShapeWipeW2B::handle_event()
102 plugin->config.direction = 0;
103 window->right->update(0);
104 plugin->send_configure_change();
108 ShapeWipeB2W::ShapeWipeB2W(ShapeWipeMain *plugin,
109 ShapeWipeWindow *window, int x, int y)
110 : BC_Radial(x, y, plugin->config.direction == 1, _("Black to White"))
112 this->plugin = plugin;
113 this->window = window;
116 int ShapeWipeB2W::handle_event()
119 plugin->config.direction = 1;
120 window->left->update(0);
121 plugin->send_configure_change();
126 ShapeWipePreserveAspectRatio::ShapeWipePreserveAspectRatio(ShapeWipeMain *plugin,
127 ShapeWipeWindow *window, int x, int y)
128 : BC_CheckBox (x, y, plugin->config.preserve_aspect, _("Preserve shape aspect ratio"))
130 this->plugin = plugin;
131 this->window = window;
134 int ShapeWipePreserveAspectRatio::handle_event()
136 plugin->config.preserve_aspect = get_value();
137 plugin->send_configure_change();
142 ShapeWipeTumble::ShapeWipeTumble(ShapeWipeMain *client,
143 ShapeWipeWindow *window, int x, int y)
146 this->client = client;
147 this->window = window;
151 int ShapeWipeTumble::handle_up_event()
153 window->prev_shape();
157 int ShapeWipeTumble::handle_down_event()
159 window->next_shape();
164 ShapeWipeFeather::ShapeWipeFeather(ShapeWipeMain *client,
165 ShapeWipeWindow *window, int x, int y)
166 : BC_TumbleTextBox(window,
167 bclip(client->config.feather, SHAPE_FMIN, SHAPE_FMAX),
168 SHAPE_FMIN, SHAPE_FMAX, x, y, xS(64), yS(3))
170 this->client = client;
171 this->window = window;
174 int ShapeWipeFeather::handle_event()
176 float v = atof(get_text());
177 bclamp(v, SHAPE_FMIN, SHAPE_FMAX);
178 client->config.feather = v;
179 float sv = log(v)/M_LN2;
180 window->shape_fslider->update(sv);
181 client->send_configure_change();
185 ShapeWipeFSlider::ShapeWipeFSlider(ShapeWipeMain *client,
186 ShapeWipeWindow *window, int x, int y, int w)
187 : BC_FSlider(x, y, 0, w, w, SHAPE_FLOG_MIN, SHAPE_FLOG_MAX,
188 log(bclip(client->config.feather, SHAPE_FMIN, SHAPE_FMAX))/M_LN2)
190 this->client = client;
191 this->window = window;
192 set_precision(0.001);
193 set_pagination(0.01, 0.1);
194 enable_show_value(0);
197 int ShapeWipeFSlider::handle_event()
199 float v = get_value();
200 float vv = exp(M_LN2*v);
201 client->config.feather = vv;
202 window->shape_feather->update(vv);
203 client->send_configure_change();
207 ShapeWipeReset::ShapeWipeReset(ShapeWipeMain *client,
208 ShapeWipeWindow *window, int x, int y)
209 : BC_Button(x, y, client->get_theme()->get_image_set("reset_button"))
211 this->client = client;
212 this->window = window;
213 set_tooltip(_("Reset feather"));
216 int ShapeWipeReset::handle_event()
218 window->shape_fslider->update(SHAPE_FLOG_MIN);
219 float v = SHAPE_FMIN;
220 window->shape_feather->update(v);
221 client->config.feather = v;
222 client->send_configure_change();
226 ShapeWipeShape::ShapeWipeShape(ShapeWipeMain *client,
227 ShapeWipeWindow *window, int x, int y,
228 int text_w, int list_h)
229 : BC_PopupTextBox(window, &window->shapes, client->config.shape_name,
230 x, y, text_w, list_h)
232 this->client = client;
233 this->window = window;
236 int ShapeWipeShape::handle_event()
238 strcpy(client->config.shape_name, get_text());
239 client->send_configure_change();
244 ShapeWipeWindow::ShapeWipeWindow(ShapeWipeMain *plugin)
245 : PluginClientWindow(plugin, xS(425), yS(215), xS(425), yS(215), 0)
247 this->plugin = plugin;
251 ShapeWipeWindow::~ShapeWipeWindow()
253 shapes.remove_all_objects();
254 delete shape_feather;
258 void ShapeWipeWindow::create_objects()
261 lock_window("ShapeWipeWindow::create_objects");
262 int pad = xS(10), margin = xS(10);
263 int x = margin, y = margin;
264 int ww = get_w() - 2*margin;
266 plugin->init_shapes();
267 for( int i=0; i<plugin->shape_titles.size(); ++i ) {
268 shapes.append(new BC_ListBoxItem(plugin->shape_titles.get(i)));
272 add_subwindow(bar = new BC_TitleBar(x, y, ww, xS(20), yS(10),
273 _("Wipe"), MEDIUMFONT));
274 y += bar->get_h() + pad;
276 add_subwindow(title = new BC_Title(x, y, _("Shape:")));
277 int x1 = xS(85), x2 = xS(355), x3 = xS(386);
278 shape_text = new ShapeWipeShape(plugin, this, x1, y, x2-x1, yS(200));
279 shape_text->create_objects();
280 add_subwindow(new ShapeWipeTumble(plugin, this, x3, y));
281 y += shape_text->get_h() + pad;
284 add_subwindow(title = new BC_Title(x, y, _("Feather:")));
286 shape_feather = new ShapeWipeFeather(plugin, this, x, y);
287 shape_feather->create_objects();
288 shape_feather->set_log_floatincrement(1);
289 x += shape_feather->get_w() + 2*pad;
290 add_subwindow(shape_fslider = new ShapeWipeFSlider(plugin, this, x, y, x2-x));
291 add_subwindow(shape_reset = new ShapeWipeReset(plugin, this, x3, y));
292 y += shape_fslider->get_h() + pad;
295 ShapeWipePreserveAspectRatio *aspect_ratio;
296 add_subwindow(aspect_ratio = new ShapeWipePreserveAspectRatio(
297 plugin, this, x, y));
298 y += aspect_ratio->get_h() + pad;
300 add_subwindow(bar = new BC_TitleBar(x, y, ww, xS(20), yS(10),
301 _("Direction"), MEDIUMFONT));
302 y += bar->get_h() + pad;
304 add_subwindow(left = new ShapeWipeW2B(plugin, this, x, y));
306 add_subwindow(right = new ShapeWipeB2W(plugin, this, x, y));
312 void ShapeWipeWindow::next_shape()
314 ShapeWipeConfig &config = plugin->config;
315 int k = plugin->shape_titles.size();
316 while( --k>=0 && strcmp(plugin->shape_titles.get(k),config.shape_name) );
319 if( ++k >= plugin->shape_titles.size() ) k = 0;
320 strcpy(config.shape_name, plugin->shape_titles.get(k));
321 shape_text->update(config.shape_name);
323 client->send_configure_change();
326 void ShapeWipeWindow::prev_shape()
328 ShapeWipeConfig &config = plugin->config;
329 int k = plugin->shape_titles.size();
330 while( --k>=0 && strcmp(plugin->shape_titles.get(k),config.shape_name) );
333 if( --k < 0 ) k = plugin->shape_titles.size()-1;
334 strcpy(config.shape_name, plugin->shape_titles.get(k));
335 shape_text->update(config.shape_name);
337 client->send_configure_change();
341 ShapeWipeMain::ShapeWipeMain(PluginServer *server)
342 : PluginVClient(server)
347 current_filename[0] = '\0';
350 last_preserve_aspect = 0;
351 shapes_initialized = 0;
352 shape_paths.set_array_delete();
353 shape_titles.set_array_delete();
356 ShapeWipeMain::~ShapeWipeMain()
358 reset_pattern_image();
359 shape_paths.remove_all_objects();
360 shape_titles.remove_all_objects();
364 const char* ShapeWipeMain::plugin_title() { return N_("Shape Wipe"); }
365 int ShapeWipeMain::is_transition() { return 1; }
366 int ShapeWipeMain::uses_gui() { return 1; }
368 NEW_WINDOW_MACRO(ShapeWipeMain, ShapeWipeWindow);
370 void ShapeWipeMain::read_data(KeyFrame *keyframe)
372 config.read_xml(keyframe);
374 void ShapeWipeMain::save_data(KeyFrame *keyframe)
376 config.save_xml(keyframe);
379 void ShapeWipeMain::init_shapes()
381 if( !shapes_initialized ) {
383 fs.set_filter("[*.png][*.jpg]");
384 char shape_path[BCTEXTLEN];
385 sprintf(shape_path, "%s%s", get_plugin_dir(), SHAPE_SEARCHPATH);
386 fs.update(shape_path);
388 for( int i=0; i<fs.total_files(); ++i ) {
389 FileItem *file_item = fs.get_entry(i);
390 if( !file_item->get_is_dir() ) {
391 shape_paths.append(cstrdup(file_item->get_path()));
392 char *ptr = cstrdup(file_item->get_name());
393 char *ptr2 = strrchr(ptr, '.');
395 shape_titles.append(ptr);
399 shapes_initialized = 1;
403 int ShapeWipeMain::load_configuration()
405 read_data(get_prev_keyframe(get_source_position()));
409 int ShapeWipeMain::read_pattern_image(char *shape_name,
410 int frame_width, int frame_height)
413 int ret = 0, fd = -1;
415 unsigned char header[10];
416 // Convert name to filename
417 int k = shape_paths.size(), hsz = sizeof(header);
418 while( --k>=0 && strcmp(shape_titles[k], shape_name) );
421 strcpy(current_filename, shape_paths[k]);
422 fd = ::open(current_filename, O_RDONLY);
423 if( fd < 0 || read(fd,header,hsz) != hsz ) ret = 1;
426 is_png = !png_sig_cmp(header, 0, hsz);
427 if( !is_png && strncmp("JFIF", (char*)header+6, 4) ) ret = 1;
430 lseek(fd, 0, SEEK_SET);
432 VFramePng::vframe_png(fd, 1, 1) :
433 VFrameJpeg::vframe_jpeg(fd, 1, 1, BC_GREY8);
434 if( !pattern ) ret = 1;
440 int width = pattern->get_w(), height = pattern->get_h();
441 double row_factor, col_factor;
442 double row_offset = 0.5, col_offset = 0.5; // for rounding
444 if( config.preserve_aspect && aspect_w && aspect_h ) {
445 row_factor = (height-1)/aspect_h;
446 col_factor = (width-1)/aspect_w;
447 if( row_factor < col_factor )
448 col_factor = row_factor;
450 row_factor = col_factor;
451 row_factor *= aspect_h/(double)(frame_height-1);
452 col_factor *= aspect_w/(double)(frame_width-1);
454 // center the pattern over the frame
455 row_offset += (height-1-(frame_height-1)*row_factor)/2;
456 col_offset += (width-1-(frame_width-1)*col_factor)/2;
459 // Stretch (or shrink) the pattern image to fill the frame
460 row_factor = (double)(height-1)/(double)(frame_height-1);
461 col_factor = (double)(width-1)/(double)(frame_width-1);
463 int out_w = width * col_factor, out_h = height * row_factor;
464 if( out_w != width || out_h != height ) {
465 VFrame *new_pattern = new VFrame(frame_width, frame_height, BC_GREY8);
466 new_pattern->transfer_from(pattern, 0, col_offset,row_offset,
467 frame_width*col_factor, frame_height*row_factor);
468 delete pattern; pattern = new_pattern;
470 unsigned char **rows = pattern->get_rows();
471 unsigned char min = 0xff, max = 0x00;
472 // first, determine range min..max
473 for( int y=0; y<frame_height; ++y ) {
474 unsigned char *row = rows[y];
475 for( int x=0; x<frame_width; ++x ) {
476 unsigned char value = row[x];
477 if( value < min ) min = value;
478 if( value > max ) max = value;
481 if( min > max ) min = max;
482 int range = max - min;
483 if( !range ) range = 1;
484 pattern_image = new unsigned char*[frame_height];
485 // scale to fade normalized pattern_image
486 for( int y=0; y<frame_height; ++y ) {
487 unsigned char *row = rows[y];
488 pattern_image[y] = new unsigned char[frame_width];
489 for( int x=0; x<frame_width; ++x ) {
490 unsigned char value = row[x];
491 pattern_image[y][x] = 0xff*(value-min) / range;
494 this->frame_width = frame_width;
495 this->frame_height = frame_height;
501 void ShapeWipeMain::reset_pattern_image()
503 if( pattern_image ) {
504 for( int y=0; y<frame_height; ++y )
505 delete [] pattern_image[y];
506 delete [] pattern_image; pattern_image = 0;
510 #define SHAPEBLEND(type, components, tmp_type) { \
511 float scale = feather ? 1/feather : 0xff; \
512 type **in_rows = (type**)input->get_rows(); \
513 type **out_rows = (type**)output->get_rows(); \
515 for( int y=y1; y<y2; ++y ) { \
516 type *in_row = (type*) in_rows[y]; \
517 type *out_row = (type*)out_rows[y]; \
518 unsigned char *pattern_row = pattern_image[y]; \
519 for( int x=0; x<w; ++x ) { \
520 tmp_type d = (pattern_row[x] - threshold) * scale; \
521 if( d > 0xff ) d = 0xff; \
522 else if( d < -0xff ) d = -0xff; \
523 tmp_type a = (d + 0xff) / 2, b = 0xff - a; \
524 for( int i=0; i<components; ++i ) { \
525 type ic = in_row[i], oc = out_row[i]; \
526 out_row[i] = (ic * a + oc * b) / 0xff; \
528 in_row += components; out_row += components; \
533 for( int y=y1; y<y2; ++y ) { \
534 type *in_row = (type*) in_rows[y]; \
535 type *out_row = (type*)out_rows[y]; \
536 unsigned char *pattern_row = pattern_image[y]; \
537 for( int x=0; x<w; ++x ) { \
538 tmp_type d = (pattern_row[x] - threshold) * scale; \
539 if( d > 0xff ) d = 0xff; \
540 else if( d < -0xff ) d = -0xff; \
541 tmp_type b = (d + 0xff) / 2, a = 0xff - b; \
542 for( int i=0; i<components; ++i ) { \
543 type ic = in_row[i], oc = out_row[i]; \
544 out_row[i] = (ic * a + oc * b) / 0xff; \
546 in_row += components; out_row += components; \
552 int ShapeWipeMain::process_realtime(VFrame *input, VFrame *output)
555 this->output = output;
556 int w = input->get_w();
557 int h = input->get_h();
559 load_configuration();
561 if( strncmp(config.shape_name, current_name, BCTEXTLEN) ||
562 config.preserve_aspect != last_preserve_aspect ) {
563 reset_pattern_image();
565 if ( !pattern_image ) {
566 if( read_pattern_image(config.shape_name, w, h) ) {
567 fprintf(stderr, _("Shape Wipe: cannot load shape %s\n"),
569 current_filename[0] = 0;
572 strncpy(current_name, config.shape_name, BCTEXTLEN);
573 last_preserve_aspect = config.preserve_aspect;
576 float fade = (float)PluginClient::get_source_position() /
577 (float)PluginClient::get_total_len();
578 if( !config.direction ) fade = 1 - fade;
579 threshold = fade * 0xff;
581 int slices = w*h/0x40000+1;
582 int max_slices = BC_Resources::machine_cpus/2;
583 if( slices > max_slices ) slices = max_slices;
584 if( slices < 1 ) slices = 1;
585 if( engine && engine->get_total_clients() != slices ) {
586 delete engine; engine = 0;
589 engine = new ShapeEngine(this, slices, slices);
591 engine->process_packages();
596 ShapePackage::ShapePackage()
601 ShapeUnit::ShapeUnit(ShapeEngine *server) : LoadClient(server)
603 this->server = server;
606 ShapeUnit::~ShapeUnit()
610 void ShapeUnit::process_package(LoadPackage *package)
612 VFrame *input = server->plugin->input;
613 VFrame *output = server->plugin->output;
614 int w = input->get_w();
615 int dir = server->plugin->config.direction;
617 unsigned char **pattern_image = server->plugin->pattern_image;
618 unsigned char threshold = server->plugin->threshold;
619 float feather = server->plugin->config.feather;
620 ShapePackage *pkg = (ShapePackage*)package;
621 int y1 = pkg->y1, y2 = pkg->y2;
623 switch(input->get_color_model()) {
625 SHAPEBLEND(float, 3, float)
629 SHAPEBLEND(unsigned char, 3, int)
632 SHAPEBLEND(float, 4, float)
636 SHAPEBLEND(unsigned char, 4, int)
640 SHAPEBLEND(uint16_t, 3, int64_t)
642 case BC_RGBA16161616:
643 case BC_YUVA16161616:
644 SHAPEBLEND(uint16_t, 4, int64_t)
650 ShapeEngine::ShapeEngine(ShapeWipeMain *plugin,
651 int total_clients, int total_packages)
652 : LoadServer(total_clients, total_packages)
654 this->plugin = plugin;
657 ShapeEngine::~ShapeEngine()
662 void ShapeEngine::init_packages()
664 int y = 0, h1 = plugin->input->get_h()-1;
665 int total_packages = get_total_packages();
666 for(int i = 0; i<total_packages; ) {
667 ShapePackage *pkg = (ShapePackage*)get_package(i++);
669 y = h1 * i / total_packages;
674 LoadClient* ShapeEngine::new_client()
676 return new ShapeUnit(this);
679 LoadPackage* ShapeEngine::new_package()
681 return new ShapePackage;