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"
39 #define SHAPE_SEARCHPATH "/shapes"
40 #define DEFAULT_SHAPE "circle"
41 // feather slider range log2 = -10 .. -1 == 9.8e-4 .. 0.5
42 #define SHAPE_FLOG_MIN -10.
43 #define SHAPE_FLOG_MAX -1.
44 #define SHAPE_FMIN expf(M_LN2*SHAPE_FLOG_MIN)
45 #define SHAPE_FMAX expf(M_LN2*SHAPE_FLOG_MAX)
47 REGISTER_PLUGIN(ShapeWipeMain)
49 ShapeWipeConfig::ShapeWipeConfig()
54 strcpy(shape_name, DEFAULT_SHAPE);
56 ShapeWipeConfig::~ShapeWipeConfig()
60 void ShapeWipeConfig::read_xml(KeyFrame *keyframe)
63 input.set_shared_input(keyframe->xbuf);
65 while( !input.read_tag() ) {
66 if( input.tag.title_is("SHAPEWIPE") ) {
67 direction = input.tag.get_property("DIRECTION", direction);
68 feather = input.tag.get_property("FEATHER", feather);
69 preserve_aspect = input.tag.get_property("PRESERVE_ASPECT", preserve_aspect);
70 input.tag.get_property("SHAPE_NAME", shape_name);
74 void ShapeWipeConfig::save_xml(KeyFrame *keyframe)
77 output.set_shared_output(keyframe->xbuf);
78 output.tag.set_title("SHAPEWIPE");
79 output.tag.set_property("DIRECTION", direction);
80 output.tag.set_property("FEATHER", feather);
81 output.tag.set_property("PRESERVE_ASPECT", preserve_aspect);
82 output.tag.set_property("SHAPE_NAME", shape_name);
84 output.tag.set_title("/SHAPEWIPE");
86 output.terminate_string();
90 ShapeWipeW2B::ShapeWipeW2B(ShapeWipeMain *plugin,
91 ShapeWipeWindow *window, int x, int y)
92 : BC_Radial(x, y, plugin->config.direction == 0, _("White to Black"))
94 this->plugin = plugin;
95 this->window = window;
98 int ShapeWipeW2B::handle_event()
101 plugin->config.direction = 0;
102 window->right->update(0);
103 plugin->send_configure_change();
107 ShapeWipeB2W::ShapeWipeB2W(ShapeWipeMain *plugin,
108 ShapeWipeWindow *window, int x, int y)
109 : BC_Radial(x, y, plugin->config.direction == 1, _("Black to White"))
111 this->plugin = plugin;
112 this->window = window;
115 int ShapeWipeB2W::handle_event()
118 plugin->config.direction = 1;
119 window->left->update(0);
120 plugin->send_configure_change();
125 ShapeWipePreserveAspectRatio::ShapeWipePreserveAspectRatio(ShapeWipeMain *plugin,
126 ShapeWipeWindow *window, int x, int y)
127 : BC_CheckBox (x, y, plugin->config.preserve_aspect, _("Preserve shape aspect ratio"))
129 this->plugin = plugin;
130 this->window = window;
133 int ShapeWipePreserveAspectRatio::handle_event()
135 plugin->config.preserve_aspect = get_value();
136 plugin->send_configure_change();
141 ShapeWipeTumble::ShapeWipeTumble(ShapeWipeMain *client,
142 ShapeWipeWindow *window, int x, int y)
145 this->client = client;
146 this->window = window;
150 int ShapeWipeTumble::handle_up_event()
152 window->prev_shape();
156 int ShapeWipeTumble::handle_down_event()
158 window->next_shape();
163 ShapeWipeFeather::ShapeWipeFeather(ShapeWipeMain *client,
164 ShapeWipeWindow *window, int x, int y)
165 : BC_TumbleTextBox(window,
166 bclip(client->config.feather, SHAPE_FMIN, SHAPE_FMAX),
167 SHAPE_FMIN, SHAPE_FMAX, x, y, xS(64), yS(3))
169 this->client = client;
170 this->window = window;
173 int ShapeWipeFeather::handle_event()
175 float v = atof(get_text());
176 bclamp(v, SHAPE_FMIN, SHAPE_FMAX);
177 client->config.feather = v;
178 float sv = log(v)/M_LN2;
179 window->shape_fslider->update(sv);
180 client->send_configure_change();
184 ShapeWipeFSlider::ShapeWipeFSlider(ShapeWipeMain *client,
185 ShapeWipeWindow *window, int x, int y, int w)
186 : BC_FSlider(x, y, 0, w, w, SHAPE_FLOG_MIN, SHAPE_FLOG_MAX,
187 log(bclip(client->config.feather, SHAPE_FMIN, SHAPE_FMAX))/M_LN2)
189 this->client = client;
190 this->window = window;
191 set_precision(0.001);
192 set_pagination(0.01, 0.1);
193 enable_show_value(0);
196 int ShapeWipeFSlider::handle_event()
198 float v = get_value();
199 float vv = exp(M_LN2*v);
200 client->config.feather = vv;
201 window->shape_feather->update(vv);
202 client->send_configure_change();
206 ShapeWipeReset::ShapeWipeReset(ShapeWipeMain *client,
207 ShapeWipeWindow *window, int x, int y)
208 : BC_Button(x, y, client->get_theme()->get_image_set("reset_button"))
210 this->client = client;
211 this->window = window;
212 set_tooltip(_("Reset feather"));
215 int ShapeWipeReset::handle_event()
217 window->shape_fslider->update(SHAPE_FLOG_MIN);
218 float v = SHAPE_FMIN;
219 window->shape_feather->update(v);
220 client->config.feather = v;
221 client->send_configure_change();
225 ShapeWipeShape::ShapeWipeShape(ShapeWipeMain *client,
226 ShapeWipeWindow *window, int x, int y,
227 int text_w, int list_h)
228 : BC_PopupTextBox(window, &window->shapes, client->config.shape_name,
229 x, y, text_w, list_h)
231 this->client = client;
232 this->window = window;
235 int ShapeWipeShape::handle_event()
237 strcpy(client->config.shape_name, get_text());
238 client->send_configure_change();
243 ShapeWipeWindow::ShapeWipeWindow(ShapeWipeMain *plugin)
244 : PluginClientWindow(plugin, xS(425), yS(215), xS(425), yS(215), 0)
246 this->plugin = plugin;
250 ShapeWipeWindow::~ShapeWipeWindow()
252 shapes.remove_all_objects();
253 delete shape_feather;
257 void ShapeWipeWindow::create_objects()
260 lock_window("ShapeWipeWindow::create_objects");
261 int pad = xS(10), margin = xS(10);
262 int x = margin, y = margin;
263 int ww = get_w() - 2*margin;
265 plugin->init_shapes();
266 for( int i=0; i<plugin->shape_titles.size(); ++i ) {
267 shapes.append(new BC_ListBoxItem(plugin->shape_titles.get(i)));
271 add_subwindow(bar = new BC_TitleBar(x, y, ww, xS(20), yS(10),
272 _("Wipe"), MEDIUMFONT));
273 y += bar->get_h() + pad;
275 add_subwindow(title = new BC_Title(x, y, _("Shape:")));
276 int x1 = xS(85), x2 = xS(355), x3 = xS(386);
277 shape_text = new ShapeWipeShape(plugin, this, x1, y, x2-x1, yS(200));
278 shape_text->create_objects();
279 add_subwindow(new ShapeWipeTumble(plugin, this, x3, y));
280 y += shape_text->get_h() + pad;
283 add_subwindow(title = new BC_Title(x, y, _("Feather:")));
285 shape_feather = new ShapeWipeFeather(plugin, this, x, y);
286 shape_feather->create_objects();
287 shape_feather->set_log_floatincrement(1);
288 x += shape_feather->get_w() + 2*pad;
289 add_subwindow(shape_fslider = new ShapeWipeFSlider(plugin, this, x, y, x2-x));
290 add_subwindow(shape_reset = new ShapeWipeReset(plugin, this, x3, y));
291 y += shape_fslider->get_h() + pad;
294 ShapeWipePreserveAspectRatio *aspect_ratio;
295 add_subwindow(aspect_ratio = new ShapeWipePreserveAspectRatio(
296 plugin, this, x, y));
297 y += aspect_ratio->get_h() + pad;
299 add_subwindow(bar = new BC_TitleBar(x, y, ww, xS(20), yS(10),
300 _("Direction"), MEDIUMFONT));
301 y += bar->get_h() + pad;
303 add_subwindow(left = new ShapeWipeW2B(plugin, this, x, y));
305 add_subwindow(right = new ShapeWipeB2W(plugin, this, x, y));
311 void ShapeWipeWindow::next_shape()
313 ShapeWipeConfig &config = plugin->config;
314 int k = plugin->shape_titles.size();
315 while( --k>=0 && strcmp(plugin->shape_titles.get(k),config.shape_name) );
318 if( ++k >= plugin->shape_titles.size() ) k = 0;
319 strcpy(config.shape_name, plugin->shape_titles.get(k));
320 shape_text->update(config.shape_name);
322 client->send_configure_change();
325 void ShapeWipeWindow::prev_shape()
327 ShapeWipeConfig &config = plugin->config;
328 int k = plugin->shape_titles.size();
329 while( --k>=0 && strcmp(plugin->shape_titles.get(k),config.shape_name) );
332 if( --k < 0 ) k = plugin->shape_titles.size()-1;
333 strcpy(config.shape_name, plugin->shape_titles.get(k));
334 shape_text->update(config.shape_name);
336 client->send_configure_change();
340 ShapeWipeMain::ShapeWipeMain(PluginServer *server)
341 : PluginVClient(server)
346 current_filename[0] = '\0';
351 last_preserve_aspect = 0;
352 shapes_initialized = 0;
353 shape_paths.set_array_delete();
354 shape_titles.set_array_delete();
357 ShapeWipeMain::~ShapeWipeMain()
359 reset_pattern_image();
360 shape_paths.remove_all_objects();
361 shape_titles.remove_all_objects();
365 const char* ShapeWipeMain::plugin_title() { return N_("Shape Wipe"); }
366 int ShapeWipeMain::is_transition() { return 1; }
367 int ShapeWipeMain::uses_gui() { return 1; }
369 NEW_WINDOW_MACRO(ShapeWipeMain, ShapeWipeWindow);
371 void ShapeWipeMain::read_data(KeyFrame *keyframe)
373 config.read_xml(keyframe);
375 void ShapeWipeMain::save_data(KeyFrame *keyframe)
377 config.save_xml(keyframe);
380 void ShapeWipeMain::init_shapes()
382 if( !shapes_initialized ) {
384 fs.set_filter("*.png");
385 char shape_path[BCTEXTLEN];
386 sprintf(shape_path, "%s%s", get_plugin_dir(), SHAPE_SEARCHPATH);
387 fs.update(shape_path);
389 for( int i=0; i<fs.total_files(); ++i ) {
390 FileItem *file_item = fs.get_entry(i);
391 if( !file_item->get_is_dir() ) {
392 shape_paths.append(cstrdup(file_item->get_path()));
393 char *ptr = cstrdup(file_item->get_name());
394 char *ptr2 = strrchr(ptr, '.');
396 shape_titles.append(ptr);
400 shapes_initialized = 1;
405 int ShapeWipeMain::load_configuration()
407 read_data(get_prev_keyframe(get_source_position()));
411 int ShapeWipeMain::read_pattern_image(char *shape_name,
412 int new_frame_width, int new_frame_height)
423 png_structp png_ptr = 0;
424 png_infop info_ptr = 0;
425 png_infop end_info = 0;
426 png_bytep *image = 0;
428 frame_width = new_frame_width;
429 frame_height = new_frame_height;
432 // Convert name to filename
433 int k = shape_paths.size();
434 while( --k>=0 && strcmp(shape_titles[k], shape_name) );
437 strcpy(current_filename, shape_paths[k]);
438 fp = fopen(current_filename, "rb");
442 fread(header, 1, 8, fp);
443 is_png = !png_sig_cmp(header, 0, 8);
444 if( !is_png ) ret = 1;
447 png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
448 if( !png_ptr ) ret = 1;
451 /* Tell libpng we already checked the first 8 bytes */
452 png_set_sig_bytes(png_ptr, 8);
453 info_ptr = png_create_info_struct(png_ptr);
454 if( !info_ptr ) ret = 1;
457 end_info = png_create_info_struct(png_ptr);
458 if( !end_info ) ret = 1;
461 png_init_io(png_ptr, fp);
462 png_read_info(png_ptr, info_ptr);
464 color_type = png_get_color_type(png_ptr, info_ptr);
465 bit_depth = png_get_bit_depth(png_ptr, info_ptr);
466 width = png_get_image_width (png_ptr, info_ptr);
467 height = png_get_image_height(png_ptr, info_ptr);
469 /* Skip the alpha channel if present
470 * stripping alpha currently doesn't work in conjunction with
471 * converting to grayscale in libpng */
472 pixel_width = color_type & PNG_COLOR_MASK_ALPHA ? 2 : 1;
473 /* Convert 16 bit data to 8 bit */
474 if( bit_depth == 16 ) png_set_strip_16(png_ptr);
475 /* Expand to 1 pixel per byte if necessary */
476 if( bit_depth < 8 ) png_set_packing(png_ptr);
478 /* Convert to grayscale */
479 if( color_type == PNG_COLOR_TYPE_RGB ||
480 color_type == PNG_COLOR_TYPE_RGB_ALPHA )
481 png_set_rgb_to_gray_fixed(png_ptr, 1, -1, -1);
483 /* Allocate memory to hold the original png image */
484 image = (png_bytep*)new png_bytep[height];
485 for( row=0; row<(int)height; ++row )
486 image[row] = new png_byte[width*pixel_width];
488 /* Allocate memory for the pattern image that will actually be
489 * used for the wipe */
490 pattern_image = new unsigned char*[frame_height];
492 png_read_image(png_ptr, image);
493 png_read_end(png_ptr, end_info);
495 double row_factor, col_factor;
496 double row_offset = 0.5, col_offset = 0.5; // for rounding
498 if( config.preserve_aspect && aspect_w && aspect_h ) {
499 row_factor = (height-1)/aspect_h;
500 col_factor = (width-1)/aspect_w;
501 if( row_factor < col_factor )
502 col_factor = row_factor;
504 row_factor = col_factor;
505 row_factor *= aspect_h/(double)(frame_height-1);
506 col_factor *= aspect_w/(double)(frame_width-1);
508 // center the pattern over the frame
509 row_offset += (height-1-(frame_height-1)*row_factor)/2;
510 col_offset += (width-1-(frame_width-1)*col_factor)/2;
513 // Stretch (or shrink) the pattern image to fill the frame
514 row_factor = (double)(height-1)/(double)(frame_height-1);
515 col_factor = (double)(width-1)/(double)(frame_width-1);
517 // first, determine range min..max
518 for( int y=0; y<frame_height; ++y ) {
519 row = (int)(row_factor*y + row_offset);
520 for( int x=0; x<frame_width; ++x ) {
521 col = (int)(col_factor*x + col_offset)*pixel_width;
522 value = image[row][col];
523 if( value < min_value ) min_value = value;
524 if( value > max_value ) max_value = value;
527 int range = max_value - min_value;
528 if( !range ) range = 1;
529 // scale to fade normalized pattern_image
530 for( int y=0; y<frame_height; ++y ) {
531 row = (int)(row_factor*y + row_offset);
532 pattern_image[y] = new unsigned char[frame_width];
533 for( int x=0; x<frame_width; ++x ) {
534 col = (int)(col_factor*x + col_offset)*pixel_width;
535 value = image[row][col];
536 pattern_image[y][x] = 0xff*(value - min_value) / range;
541 if( png_ptr || info_ptr || end_info )
542 png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
546 for( row=0; row<(int)height; ++row )
547 delete [] image[row];
553 void ShapeWipeMain::reset_pattern_image()
555 if( pattern_image ) {
556 for( int y=0; y<frame_height; ++y )
557 delete [] pattern_image[y];
558 delete [] pattern_image; pattern_image = 0;
560 max_value = 0; // updated in read_pattern_image
564 #define SHAPEBLEND(type, components, tmp_type) { \
565 float scale = feather ? 1/feather : 0xff; \
566 type **in_rows = (type**)input->get_rows(); \
567 type **out_rows = (type**)output->get_rows(); \
569 for( int y=y1; y<y2; ++y ) { \
570 type *in_row = (type*) in_rows[y]; \
571 type *out_row = (type*)out_rows[y]; \
572 unsigned char *pattern_row = pattern_image[y]; \
573 for( int x=0; x<w; ++x ) { \
574 tmp_type d = (pattern_row[x] - threshold) * scale; \
575 if( d > 0xff ) d = 0xff; \
576 else if( d < -0xff ) d = -0xff; \
577 tmp_type a = (d + 0xff) / 2, b = 0xff - a; \
578 for( int i=0; i<components; ++i ) { \
579 type ic = in_row[i], oc = out_row[i]; \
580 out_row[i] = (ic * a + oc * b) / 0xff; \
582 in_row += components; out_row += components; \
587 for( int y=y1; y<y2; ++y ) { \
588 type *in_row = (type*) in_rows[y]; \
589 type *out_row = (type*)out_rows[y]; \
590 unsigned char *pattern_row = pattern_image[y]; \
591 for( int x=0; x<w; ++x ) { \
592 tmp_type d = (pattern_row[x] - threshold) * scale; \
593 if( d > 0xff ) d = 0xff; \
594 else if( d < -0xff ) d = -0xff; \
595 tmp_type b = (d + 0xff) / 2, a = 0xff - b; \
596 for( int i=0; i<components; ++i ) { \
597 type ic = in_row[i], oc = out_row[i]; \
598 out_row[i] = (ic * a + oc * b) / 0xff; \
600 in_row += components; out_row += components; \
606 int ShapeWipeMain::process_realtime(VFrame *input, VFrame *output)
609 this->output = output;
610 int w = input->get_w();
611 int h = input->get_h();
613 load_configuration();
615 if( strncmp(config.shape_name, current_name, BCTEXTLEN) ||
616 config.preserve_aspect != last_preserve_aspect ) {
617 reset_pattern_image();
619 if ( !pattern_image ) {
620 if( read_pattern_image(config.shape_name, w, h) ) {
621 fprintf(stderr, _("Shape Wipe: cannot load shape %s\n"),
623 current_filename[0] = 0;
626 strncpy(current_name, config.shape_name, BCTEXTLEN);
627 last_preserve_aspect = config.preserve_aspect;
630 float fade = (float)PluginClient::get_source_position() /
631 (float)PluginClient::get_total_len();
632 if( !config.direction ) fade = 1 - fade;
633 threshold = fade * 0xff;
635 int slices = w*h/0x40000+1;
636 int max_slices = BC_Resources::machine_cpus/2;
637 if( slices > max_slices ) slices = max_slices;
638 if( slices < 1 ) slices = 1;
639 if( engine && engine->get_total_clients() != slices ) {
640 delete engine; engine = 0;
643 engine = new ShapeEngine(this, slices, slices);
645 engine->process_packages();
650 ShapePackage::ShapePackage()
655 ShapeUnit::ShapeUnit(ShapeEngine *server) : LoadClient(server)
657 this->server = server;
660 ShapeUnit::~ShapeUnit()
664 void ShapeUnit::process_package(LoadPackage *package)
666 VFrame *input = server->plugin->input;
667 VFrame *output = server->plugin->output;
668 int w = input->get_w();
669 int dir = server->plugin->config.direction;
671 unsigned char **pattern_image = server->plugin->pattern_image;
672 unsigned char threshold = server->plugin->threshold;
673 float feather = server->plugin->config.feather;
674 ShapePackage *pkg = (ShapePackage*)package;
675 int y1 = pkg->y1, y2 = pkg->y2;
677 switch(input->get_color_model()) {
679 SHAPEBLEND(float, 3, float)
683 SHAPEBLEND(unsigned char, 3, int)
686 SHAPEBLEND(float, 4, float)
690 SHAPEBLEND(unsigned char, 4, int)
694 SHAPEBLEND(uint16_t, 3, int64_t)
696 case BC_RGBA16161616:
697 case BC_YUVA16161616:
698 SHAPEBLEND(uint16_t, 4, int64_t)
704 ShapeEngine::ShapeEngine(ShapeWipeMain *plugin,
705 int total_clients, int total_packages)
706 : LoadServer(total_clients, total_packages)
708 this->plugin = plugin;
711 ShapeEngine::~ShapeEngine()
716 void ShapeEngine::init_packages()
718 int y = 0, h1 = plugin->input->get_h()-1;
719 int total_packages = get_total_packages();
720 for(int i = 0; i<total_packages; ) {
721 ShapePackage *pkg = (ShapePackage*)get_package(i++);
723 y = h1 * i / total_packages;
728 LoadClient* ShapeEngine::new_client()
730 return new ShapeUnit(this);
733 LoadPackage* ShapeEngine::new_package()
735 return new ShapePackage;