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"
42 REGISTER_PLUGIN(ShapeWipeMain)
44 ShapeWipeW2B::ShapeWipeW2B(ShapeWipeMain *plugin,
45 ShapeWipeWindow *window,
50 plugin->direction == 0,
53 this->plugin = plugin;
54 this->window = window;
57 int ShapeWipeW2B::handle_event()
60 plugin->direction = 0;
61 window->right->update(0);
62 plugin->send_configure_change();
66 ShapeWipeB2W::ShapeWipeB2W(ShapeWipeMain *plugin,
67 ShapeWipeWindow *window,
72 plugin->direction == 1,
75 this->plugin = plugin;
76 this->window = window;
79 int ShapeWipeB2W::handle_event()
82 plugin->direction = 1;
83 window->left->update(0);
84 plugin->send_configure_change();
88 ShapeWipeAntiAlias::ShapeWipeAntiAlias(ShapeWipeMain *plugin,
89 ShapeWipeWindow *window,
92 : BC_CheckBox (x,y,plugin->antialias, _("Anti-aliasing"))
94 this->plugin = plugin;
95 this->window = window;
98 int ShapeWipeAntiAlias::handle_event()
100 plugin->antialias = get_value();
101 plugin->send_configure_change();
105 ShapeWipePreserveAspectRatio::ShapeWipePreserveAspectRatio(ShapeWipeMain *plugin,
106 ShapeWipeWindow *window,
109 : BC_CheckBox (x, y, plugin->preserve_aspect, _("Preserve shape aspect ratio"))
111 this->plugin = plugin;
112 this->window = window;
115 int ShapeWipePreserveAspectRatio::handle_event()
117 plugin->preserve_aspect = get_value();
118 plugin->send_configure_change();
127 ShapeWipeTumble::ShapeWipeTumble(ShapeWipeMain *client,
128 ShapeWipeWindow *window,
133 this->client = client;
134 this->window = window;
137 int ShapeWipeTumble::handle_up_event()
139 window->prev_shape();
143 int ShapeWipeTumble::handle_down_event()
145 window->next_shape();
155 ShapeWipeShape::ShapeWipeShape(ShapeWipeMain *client,
156 ShapeWipeWindow *window,
161 : BC_PopupTextBox(window,
169 this->client = client;
170 this->window = window;
173 int ShapeWipeShape::handle_event()
175 strcpy(client->shape_name, get_text());
176 client->send_configure_change();
187 ShapeWipeWindow::ShapeWipeWindow(ShapeWipeMain *plugin)
188 : PluginClientWindow(plugin,
195 this->plugin = plugin;
198 ShapeWipeWindow::~ShapeWipeWindow()
200 shapes.remove_all_objects();
205 void ShapeWipeWindow::create_objects()
208 lock_window("ShapeWipeWindow::create_objects");
209 int widget_border = plugin->get_theme()->widget_border;
210 int window_border = plugin->get_theme()->window_border;
211 int x = window_border, y = window_border;
213 plugin->init_shapes();
214 for(int i = 0; i < plugin->shape_titles.size(); i++)
216 shapes.append(new BC_ListBoxItem(plugin->shape_titles.get(i)));
219 add_subwindow(title = new BC_Title(x, y, _("Direction:")));
220 x += title->get_w() + widget_border;
221 add_subwindow(left = new ShapeWipeW2B(plugin,
225 x += left->get_w() + widget_border;
226 add_subwindow(right = new ShapeWipeB2W(plugin,
231 y += right->get_h() + widget_border;
234 add_subwindow(title = new BC_Title(x, y, _("Shape:")));
235 x += title->get_w() + widget_border;
237 // add_subwindow(filename_widget = new
238 // ShapeWipeFilename(plugin,
244 // add_subwindow(new ShapeWipeBrowseButton(
251 shape_text = new ShapeWipeShape(plugin,
257 shape_text->create_objects();
258 x += shape_text->get_w() + widget_border;
259 add_subwindow(new ShapeWipeTumble(plugin,
264 y += shape_text->get_h() + widget_border;
266 ShapeWipeAntiAlias *anti_alias;
267 add_subwindow(anti_alias = new ShapeWipeAntiAlias(
272 y += anti_alias->get_h() + widget_border;
273 ShapeWipePreserveAspectRatio *aspect_ratio;
274 add_subwindow(aspect_ratio = new ShapeWipePreserveAspectRatio(
279 y += aspect_ratio->get_h() + widget_border;
285 void ShapeWipeWindow::next_shape()
287 for(int i = 0; i < plugin->shape_titles.size(); i++)
289 if(!strcmp(plugin->shape_titles.get(i), plugin->shape_name))
292 if(i >= plugin->shape_titles.size()) i = 0;
293 strcpy(plugin->shape_name, plugin->shape_titles.get(i));
294 shape_text->update(plugin->shape_name);
298 client->send_configure_change();
301 void ShapeWipeWindow::prev_shape()
303 for(int i = 0; i < plugin->shape_titles.size(); i++)
305 if(!strcmp(plugin->shape_titles.get(i), plugin->shape_name))
308 if(i < 0) i = plugin->shape_titles.size() - 1;
309 strcpy(plugin->shape_name, plugin->shape_titles.get(i));
310 shape_text->update(plugin->shape_name);
314 client->send_configure_change();
321 ShapeWipeMain::ShapeWipeMain(PluginServer *server)
322 : PluginVClient(server)
326 last_read_filename[0] = '\0';
327 strcpy(shape_name, DEFAULT_SHAPE);
329 pattern_image = NULL;
330 min_value = (unsigned char)255;
331 max_value = (unsigned char)0;
334 last_preserve_aspect = 0;
335 shapes_initialized = 0;
336 shape_paths.set_array_delete();
337 shape_titles.set_array_delete();
340 ShapeWipeMain::~ShapeWipeMain()
342 reset_pattern_image();
343 shape_paths.remove_all_objects();
344 shape_titles.remove_all_objects();
347 const char* ShapeWipeMain::plugin_title() { return N_("Shape Wipe"); }
348 int ShapeWipeMain::is_transition() { return 1; }
349 int ShapeWipeMain::uses_gui() { return 1; }
351 NEW_WINDOW_MACRO(ShapeWipeMain, ShapeWipeWindow);
355 void ShapeWipeMain::save_data(KeyFrame *keyframe)
358 output.set_shared_output(keyframe->xbuf);
359 output.tag.set_title("SHAPEWIPE");
360 output.tag.set_property("DIRECTION", direction);
361 output.tag.set_property("ANTIALIAS", antialias);
362 output.tag.set_property("PRESERVE_ASPECT", preserve_aspect);
363 output.tag.set_property("FILENAME", filename);
364 output.tag.set_property("SHAPE_NAME", shape_name);
366 output.tag.set_title("/SHAPEWIPE");
368 output.terminate_string();
371 void ShapeWipeMain::read_data(KeyFrame *keyframe)
375 input.set_shared_input(keyframe->xbuf);
377 while(!input.read_tag())
379 if(input.tag.title_is("SHAPEWIPE"))
381 direction = input.tag.get_property("DIRECTION", direction);
382 antialias = input.tag.get_property("ANTIALIAS", antialias);
383 preserve_aspect = input.tag.get_property("PRESERVE_ASPECT", preserve_aspect);
384 input.tag.get_property("FILENAME", filename);
385 input.tag.get_property("SHAPE_NAME", shape_name);
390 void ShapeWipeMain::init_shapes()
392 if(!shapes_initialized)
395 fs.set_filter("*.png");
396 char shape_path[BCTEXTLEN];
397 sprintf(shape_path, "%s%s", get_plugin_dir(), SHAPE_SEARCHPATH);
398 fs.update(shape_path);
400 for(int i = 0; i < fs.total_files(); i++)
402 FileItem *file_item = fs.get_entry(i);
403 if(!file_item->get_is_dir())
405 shape_paths.append(cstrdup(file_item->get_path()));
406 char *ptr = cstrdup(file_item->get_name());
407 char *ptr2 = strrchr(ptr, '.');
409 shape_titles.append(ptr);
413 shapes_initialized = 1;
418 int ShapeWipeMain::load_configuration()
420 read_data(get_prev_keyframe(get_source_position()));
424 int ShapeWipeMain::read_pattern_image(int new_frame_width, int new_frame_height)
442 frame_width = new_frame_width;
443 frame_height = new_frame_height;
445 // Convert name to filename
446 for(int i = 0; i < shape_paths.size(); i++)
448 if(!strcmp(shape_titles.get(i), shape_name))
450 strcpy(filename, shape_paths.get(i));
455 FILE *fp = fopen(filename, "rb");
461 fread(header, 1, 8, fp);
462 is_png = !png_sig_cmp(header, 0, 8);
470 png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
479 /* Tell libpng we already checked the first 8 bytes */
480 png_set_sig_bytes(png_ptr, 8);
482 info_ptr = png_create_info_struct(png_ptr);
485 png_destroy_read_struct(&png_ptr, NULL, NULL);
490 end_info = png_create_info_struct(png_ptr);
493 png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
498 png_init_io(png_ptr, fp);
499 png_read_info(png_ptr, info_ptr);
501 color_type = png_get_color_type(png_ptr, info_ptr);
502 bit_depth = png_get_bit_depth(png_ptr, info_ptr);
503 width = png_get_image_width (png_ptr, info_ptr);
504 height = png_get_image_height(png_ptr, info_ptr);
506 /* Skip the alpha channel if present
507 * stripping alpha currently doesn't work in conjunction with
508 * converting to grayscale in libpng */
509 if (color_type & PNG_COLOR_MASK_ALPHA)
514 /* Convert 16 bit data to 8 bit */
515 if (bit_depth == 16) png_set_strip_16(png_ptr);
517 /* Expand to 1 pixel per byte if necessary */
518 if (bit_depth < 8) png_set_packing(png_ptr);
520 /* Convert to grayscale */
521 if (color_type == PNG_COLOR_TYPE_RGB ||
522 color_type == PNG_COLOR_TYPE_RGB_ALPHA)
523 png_set_rgb_to_gray_fixed(png_ptr, 1, -1, -1);
525 /* Allocate memory to hold the original png image */
526 image = (png_bytep*)malloc(sizeof(png_bytep)*height);
527 for (row = 0; row < (int)height; row++)
529 image[row] = (png_byte*)malloc(sizeof(png_byte)*width*pixel_width);
532 /* Allocate memory for the pattern image that will actually be
533 * used for the wipe */
534 pattern_image = (unsigned char**)malloc(sizeof(unsigned char*)*frame_height);
537 png_read_image(png_ptr, image);
538 png_read_end(png_ptr, end_info);
540 double row_factor, col_factor;
541 double row_offset = 0.5, col_offset = 0.5; // for rounding
543 if (preserve_aspect && aspect_w != 0 && aspect_h != 0)
545 row_factor = (height-1)/aspect_h;
546 col_factor = (width-1)/aspect_w;
547 if (row_factor < col_factor)
548 col_factor = row_factor;
550 row_factor = col_factor;
551 row_factor *= aspect_h/(double)(frame_height-1);
552 col_factor *= aspect_w/(double)(frame_width-1);
554 // center the pattern over the frame
555 row_offset += (height-1-(frame_height-1)*row_factor)/2;
556 col_offset += (width-1-(frame_width-1)*col_factor)/2;
560 // Stretch (or shrink) the pattern image to fill the frame
561 row_factor = (double)(height-1)/(double)(frame_height-1);
562 col_factor = (double)(width-1)/(double)(frame_width-1);
565 for (scaled_row = 0; scaled_row < frame_height; scaled_row++)
567 row = (int)(row_factor*scaled_row + row_offset);
568 pattern_image[scaled_row] = (unsigned char*)malloc(sizeof(unsigned char)*frame_width);
569 for (scaled_col = 0; scaled_col < frame_width; scaled_col++)
571 col = (int)(col_factor*scaled_col + col_offset)*pixel_width;
572 value = image[row][col];
573 pattern_image[scaled_row][scaled_col] = value;
574 if (value < min_value) min_value = value;
575 if (value > max_value) max_value = value;
580 png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
582 /* Deallocate the original image as it is no longer needed */
583 for (row = 0; row < (int)height; row++)
591 void ShapeWipeMain::reset_pattern_image()
594 if (pattern_image != NULL)
596 for (row = 0; row < frame_height; row++)
598 free (pattern_image[row]);
600 free (pattern_image);
601 pattern_image = NULL;
602 min_value = (unsigned char)255;
603 max_value = (unsigned char)0; // are recalc'd in read_pattern_image
607 #define SHAPEWIPE(type, components) \
610 type **in_rows = (type**)incoming->get_rows(); \
611 type **out_rows = (type**)outgoing->get_rows(); \
617 for(j = 0; j < h; j++) { \
618 in_row = (type*) in_rows[j]; \
619 out_row = (type*)out_rows[j]; \
620 pattern_row = pattern_image[j]; \
623 for(k = 0; k < w; k++, col_offset += components ) { \
624 value = pattern_row[k]; \
625 if (value < threshold) continue; \
626 out_row[col_offset] = in_row[col_offset]; \
627 out_row[col_offset + 1] = in_row[col_offset + 1]; \
628 out_row[col_offset + 2] = in_row[col_offset + 2]; \
629 if(components == 4) \
630 out_row[col_offset + 3] = in_row[col_offset + 3]; \
635 for(j = 0; j < h; j++) { \
636 in_row = (type*) in_rows[j]; \
637 out_row = (type*)out_rows[j]; \
638 pattern_row = pattern_image[j]; \
641 for(k = 0; k < w; k++, col_offset += components ) { \
642 value = pattern_row[k]; \
643 if (value > threshold) continue; \
644 out_row[col_offset] = in_row[col_offset]; \
645 out_row[col_offset + 1] = in_row[col_offset + 1]; \
646 out_row[col_offset + 2] = in_row[col_offset + 2]; \
647 if(components == 4) \
648 out_row[col_offset + 3] = in_row[col_offset + 3]; \
654 #define COMPARE1(x,y) \
656 if (pattern_image[x][y] <= threshold) opacity++; \
659 #define COMPARE2(x,y) \
661 if (pattern_image[x][y] >= threshold) opacity++; \
664 // components is always 4
665 #define BLEND_ONLY_4_NORMAL(temp_type, type, max, chroma_offset,x,y) \
667 const int bits = sizeof(type) * 8; \
668 temp_type blend_opacity = (temp_type)(alpha * ((temp_type)1 << bits) + 0.5); \
669 temp_type blend_transparency = ((temp_type)1 << bits) - blend_opacity; \
672 type* in_row = (type*)incoming->get_rows()[x]; \
673 type* output = (type*)outgoing->get_rows()[x]; \
675 output[col] = ((temp_type)in_row[col] * blend_opacity + output[col] * blend_transparency) >> bits; \
676 output[col+1] = ((temp_type)in_row[col+1] * blend_opacity + output[col+1] * blend_transparency) >> bits; \
677 output[col+2] = ((temp_type)in_row[col+2] * blend_opacity + output[col+2] * blend_transparency) >> bits; \
681 // components is always 3
682 #define BLEND_ONLY_3_NORMAL(temp_type, type, max, chroma_offset,x,y) \
684 const int bits = sizeof(type) * 8; \
685 temp_type blend_opacity = (temp_type)(alpha * ((temp_type)1 << bits) + 0.5); \
686 temp_type blend_transparency = ((temp_type)1 << bits) - blend_opacity; \
689 type* in_row = (type*)incoming->get_rows()[x]; \
690 type* output = (type*)outgoing->get_rows()[x]; \
692 output[col] = ((temp_type)in_row[col] * blend_opacity + output[col] * blend_transparency) >> bits; \
693 output[col+1] = ((temp_type)in_row[col+1] * blend_opacity + output[col+1] * blend_transparency) >> bits; \
694 output[col+2] = ((temp_type)in_row[col+2] * blend_opacity + output[col+2] * blend_transparency) >> bits; \
697 /* opacity is defined as opacity of incoming frame */
698 #define BLEND(x,y,total) \
700 float pixel_opacity = (float)opacity / total; \
701 float alpha = pixel_opacity; \
702 float pixel_transparency = 1.0 - pixel_opacity; \
705 if (pixel_opacity > 0.0) \
707 switch(incoming->get_color_model()) \
711 float *in_row = (float*)incoming->get_rows()[x]; \
712 float *out_row = (float*)outgoing->get_rows()[x]; \
714 out_row[col] = in_row[col] * pixel_opacity + \
715 out_row[col] * pixel_transparency; \
716 out_row[col+1] = in_row[col+1] * pixel_opacity + \
717 out_row[col+1] * pixel_transparency; \
718 out_row[col+2] = in_row[col+2] * pixel_opacity + \
719 out_row[col+2] * pixel_transparency; \
722 case BC_RGBA_FLOAT: \
724 float *in_row = (float*)incoming->get_rows()[x]; \
725 float *out_row = (float*)outgoing->get_rows()[x]; \
727 out_row[col] = in_row[col] * pixel_opacity + \
728 out_row[col] * pixel_transparency; \
729 out_row[col+1] = in_row[col+1] * pixel_opacity + \
730 out_row[col+1] * pixel_transparency; \
731 out_row[col+2] = in_row[col+2] * pixel_opacity + \
732 out_row[col+2] * pixel_transparency; \
736 BLEND_ONLY_3_NORMAL(uint32_t, unsigned char, 0xff, 0,x,y); \
739 BLEND_ONLY_3_NORMAL(int32_t, unsigned char, 0xff, 0x80,x,y); \
742 BLEND_ONLY_4_NORMAL(uint32_t, unsigned char, 0xff, 0,x,y); \
745 BLEND_ONLY_4_NORMAL(int32_t, unsigned char, 0xff, 0x80,x,y); \
748 BLEND_ONLY_3_NORMAL(uint64_t, uint16_t, 0xffff, 0,x,y); \
751 BLEND_ONLY_3_NORMAL(int64_t, uint16_t, 0xffff, 0x8000,x,y); \
753 case BC_RGBA16161616: \
754 BLEND_ONLY_4_NORMAL(uint64_t, uint16_t, 0xffff, 0,x,y); \
756 case BC_YUVA16161616: \
757 BLEND_ONLY_4_NORMAL(int64_t, uint16_t, 0xffff, 0x8000,x,y); \
763 int ShapeWipeMain::process_realtime(VFrame *incoming, VFrame *outgoing)
765 unsigned char *pattern_row;
767 unsigned char threshold;
773 load_configuration();
775 int w = incoming->get_w();
776 int h = incoming->get_h();
778 if (strncmp(filename, last_read_filename, BCTEXTLEN) ||
779 strncmp(shape_name, current_name, BCTEXTLEN) ||
780 preserve_aspect != last_preserve_aspect)
782 reset_pattern_image();
787 read_pattern_image(w, h);
788 strncpy(last_read_filename, filename, BCTEXTLEN);
789 last_preserve_aspect = preserve_aspect;
793 strncpy(last_read_filename, filename, BCTEXTLEN);
794 strncpy(current_name, shape_name, BCTEXTLEN);
795 last_preserve_aspect = preserve_aspect;
798 fprintf(stderr, _("Shape Wipe: cannot load shape %s\n"), filename);
799 last_read_filename[0] = 0;
806 threshold = (unsigned char)(
807 (float)PluginClient::get_source_position() /
808 (float)PluginClient::get_total_len() *
809 (float)(max_value - min_value))
814 threshold = (unsigned char)((max_value - min_value) - (
815 (float)PluginClient::get_source_position() /
816 (float)PluginClient::get_total_len() *
817 (float)(max_value - min_value)))
825 /* Top left corner */
834 for (k = 1; k < w-1; k++)
846 /* Top right corner */
855 for (j = 1; j < h-1; j++)
868 for (j = 1; j < h-1; j++)
870 for (k = 1; k < w-1; k++)
887 for (j = 1; j < h-1; j++)
899 /* Bottom left corner */
908 for (k = 1; k < w-1; k++)
920 /* Bottom right corner */
930 /* Top left corner */
939 for (k = 1; k < w-1; k++)
951 /* Top right corner */
960 for (j = 1; j < h-1; j++)
973 for (j = 1; j < h-1; j++)
975 for (k = 1; k < w-1; k++)
992 for (j = 1; j < h-1; j++)
1004 /* Bottom left corner */
1013 for (k = 1; k < w-1; k++)
1025 /* Bottom right corner */
1036 switch(incoming->get_color_model())
1043 SHAPEWIPE(unsigned char, 3)
1050 SHAPEWIPE(unsigned char, 4)
1054 SHAPEWIPE(uint16_t, 3)
1056 case BC_RGBA16161616:
1057 case BC_YUVA16161616:
1058 SHAPEWIPE(uint16_t, 4)