/* * CINELERRA * Copyright (C) 2008 Adam Williams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include "bcdisplayinfo.h" #include "bchash.h" #include "cstrdup.h" #include "edl.inc" #include "filesystem.h" #include "filexml.h" #include "language.h" #include "overlayframe.h" #include "theme.h" #include "vframe.h" #include "shapewipe.h" #include #include #include #include #define SHAPE_SEARCHPATH "/shapes" #define DEFAULT_SHAPE "circle" // feather slider range log2 = -10 .. -1 == 9.8e-4 .. 0.5 #define SHAPE_FLOG_MIN -10. #define SHAPE_FLOG_MAX -1. REGISTER_PLUGIN(ShapeWipeMain) ShapeWipeConfig::ShapeWipeConfig() { direction = 0; feather = 0; preserve_aspect = 0; strcpy(shape_name, DEFAULT_SHAPE); } ShapeWipeConfig::~ShapeWipeConfig() { } void ShapeWipeConfig::read_xml(KeyFrame *keyframe) { FileXML input; input.set_shared_input(keyframe->xbuf); while( !input.read_tag() ) { if( input.tag.title_is("SHAPEWIPE") ) { direction = input.tag.get_property("DIRECTION", direction); feather = input.tag.get_property("FEATHER", feather); preserve_aspect = input.tag.get_property("PRESERVE_ASPECT", preserve_aspect); input.tag.get_property("SHAPE_NAME", shape_name); } } } void ShapeWipeConfig::save_xml(KeyFrame *keyframe) { FileXML output; output.set_shared_output(keyframe->xbuf); output.tag.set_title("SHAPEWIPE"); output.tag.set_property("DIRECTION", direction); output.tag.set_property("FEATHER", feather); output.tag.set_property("PRESERVE_ASPECT", preserve_aspect); output.tag.set_property("SHAPE_NAME", shape_name); output.append_tag(); output.tag.set_title("/SHAPEWIPE"); output.append_tag(); output.terminate_string(); } ShapeWipeW2B::ShapeWipeW2B(ShapeWipeMain *plugin, ShapeWipeWindow *window, int x, int y) : BC_Radial(x, y, plugin->config.direction == 0, _("White to Black")) { this->plugin = plugin; this->window = window; } int ShapeWipeW2B::handle_event() { update(1); plugin->config.direction = 0; window->right->update(0); plugin->send_configure_change(); return 0; } ShapeWipeB2W::ShapeWipeB2W(ShapeWipeMain *plugin, ShapeWipeWindow *window, int x, int y) : BC_Radial(x, y, plugin->config.direction == 1, _("Black to White")) { this->plugin = plugin; this->window = window; } int ShapeWipeB2W::handle_event() { update(1); plugin->config.direction = 1; window->left->update(0); plugin->send_configure_change(); return 0; } ShapeWipePreserveAspectRatio::ShapeWipePreserveAspectRatio(ShapeWipeMain *plugin, ShapeWipeWindow *window, int x, int y) : BC_CheckBox (x, y, plugin->config.preserve_aspect, _("Preserve shape aspect ratio")) { this->plugin = plugin; this->window = window; } int ShapeWipePreserveAspectRatio::handle_event() { plugin->config.preserve_aspect = get_value(); plugin->send_configure_change(); return 0; } ShapeWipeTumble::ShapeWipeTumble(ShapeWipeMain *client, ShapeWipeWindow *window, int x, int y) : BC_Tumbler(x, y) { this->client = client; this->window = window; set_increment(0.01); } int ShapeWipeTumble::handle_up_event() { window->prev_shape(); return 1; } int ShapeWipeTumble::handle_down_event() { window->next_shape(); return 0; } ShapeWipeFeather::ShapeWipeFeather(ShapeWipeMain *client, ShapeWipeWindow *window, int x, int y) : BC_FSlider(x, y, 0, 150, 150, SHAPE_FLOG_MIN, SHAPE_FLOG_MAX, !client->config.feather ? SHAPE_FLOG_MIN : log(client->config.feather)/M_LN2) { this->client = client; this->window = window; set_precision(0.001); set_pagination(0.01, 0.1); } char *ShapeWipeFeather::get_caption() { double v = get_value(); char *caption = BC_Slider::get_caption(); sprintf(caption, "%-5.3f", exp(v*M_LN2)); return caption; } int ShapeWipeFeather::handle_event() { float v = get_value(); client->config.feather = exp(M_LN2*v); client->send_configure_change(); return 1; } ShapeWipeShape::ShapeWipeShape(ShapeWipeMain *client, ShapeWipeWindow *window, int x, int y, int text_w, int list_h) : BC_PopupTextBox(window, &window->shapes, client->config.shape_name, x, y, text_w, list_h) { this->client = client; this->window = window; } int ShapeWipeShape::handle_event() { strcpy(client->config.shape_name, get_text()); client->send_configure_change(); return 1; } ShapeWipeWindow::ShapeWipeWindow(ShapeWipeMain *plugin) : PluginClientWindow(plugin, 450, 125, 450, 125, 0) { this->plugin = plugin; } ShapeWipeWindow::~ShapeWipeWindow() { shapes.remove_all_objects(); } void ShapeWipeWindow::create_objects() { BC_Title *title = 0; lock_window("ShapeWipeWindow::create_objects"); int widget_border = plugin->get_theme()->widget_border; int window_border = plugin->get_theme()->window_border; int x = window_border, y = window_border; plugin->init_shapes(); for( int i=0; ishape_titles.size(); ++i ) { shapes.append(new BC_ListBoxItem(plugin->shape_titles.get(i))); } add_subwindow(title = new BC_Title(x, y, _("Direction:"))); x += title->get_w() + widget_border; add_subwindow(left = new ShapeWipeW2B(plugin, this, x, y)); x += left->get_w() + widget_border; add_subwindow(right = new ShapeWipeB2W(plugin, this, x, y)); x = window_border; y += right->get_h() + widget_border; add_subwindow(title = new BC_Title(x, y, _("Shape:"))); x += title->get_w() + widget_border; shape_text = new ShapeWipeShape(plugin, this, x, y, 150, 200); shape_text->create_objects(); x += shape_text->get_w() + widget_border; add_subwindow(new ShapeWipeTumble(plugin, this, x, y)); y += shape_text->get_h() + widget_border; x = window_border; add_subwindow(title = new BC_Title(x, y, _("Feather:"))); x += title->get_w() + widget_border; add_subwindow(shape_feather = new ShapeWipeFeather(plugin, this, x, y)); y += shape_feather->get_h() + widget_border; x = window_border; ShapeWipePreserveAspectRatio *aspect_ratio; add_subwindow(aspect_ratio = new ShapeWipePreserveAspectRatio( plugin, this, x, y)); y += aspect_ratio->get_h() + widget_border; show_window(); unlock_window(); } void ShapeWipeWindow::next_shape() { ShapeWipeConfig &config = plugin->config; int k = plugin->shape_titles.size(); while( --k>=0 && strcmp(plugin->shape_titles.get(k),config.shape_name) ); if( k >= 0 ) { if( ++k >= plugin->shape_titles.size() ) k = 0; strcpy(config.shape_name, plugin->shape_titles.get(k)); shape_text->update(config.shape_name); } client->send_configure_change(); } void ShapeWipeWindow::prev_shape() { ShapeWipeConfig &config = plugin->config; int k = plugin->shape_titles.size(); while( --k>=0 && strcmp(plugin->shape_titles.get(k),config.shape_name) ); if( k >= 0 ) { if( --k < 0 ) k = plugin->shape_titles.size()-1; strcpy(config.shape_name, plugin->shape_titles.get(k)); shape_text->update(config.shape_name); } client->send_configure_change(); } ShapeWipeMain::ShapeWipeMain(PluginServer *server) : PluginVClient(server) { input = 0; output = 0; engine = 0; current_filename[0] = '\0'; current_name[0] = 0; pattern_image = 0; min_value = 255; max_value = 0; last_preserve_aspect = 0; shapes_initialized = 0; shape_paths.set_array_delete(); shape_titles.set_array_delete(); } ShapeWipeMain::~ShapeWipeMain() { reset_pattern_image(); shape_paths.remove_all_objects(); shape_titles.remove_all_objects(); delete engine; } const char* ShapeWipeMain::plugin_title() { return N_("Shape Wipe"); } int ShapeWipeMain::is_transition() { return 1; } int ShapeWipeMain::uses_gui() { return 1; } NEW_WINDOW_MACRO(ShapeWipeMain, ShapeWipeWindow); void ShapeWipeMain::read_data(KeyFrame *keyframe) { config.read_xml(keyframe); } void ShapeWipeMain::save_data(KeyFrame *keyframe) { config.save_xml(keyframe); } void ShapeWipeMain::init_shapes() { if( !shapes_initialized ) { FileSystem fs; fs.set_filter("*.png"); char shape_path[BCTEXTLEN]; sprintf(shape_path, "%s%s", get_plugin_dir(), SHAPE_SEARCHPATH); fs.update(shape_path); for( int i=0; iget_is_dir() ) { shape_paths.append(cstrdup(file_item->get_path())); char *ptr = cstrdup(file_item->get_name()); char *ptr2 = strrchr(ptr, '.'); if(ptr2) *ptr2 = 0; shape_titles.append(ptr); } } shapes_initialized = 1; } } int ShapeWipeMain::load_configuration() { read_data(get_prev_keyframe(get_source_position())); return 1; } int ShapeWipeMain::read_pattern_image(char *shape_name, int new_frame_width, int new_frame_height) { png_byte header[8]; int is_png; int row, col; int pixel_width; unsigned char value; png_uint_32 width; png_uint_32 height; png_byte color_type; png_byte bit_depth; png_structp png_ptr = 0; png_infop info_ptr = 0; png_infop end_info = 0; png_bytep *image = 0; FILE *fp = 0; frame_width = new_frame_width; frame_height = new_frame_height; int ret = 0; // Convert name to filename int k = shape_paths.size(); while( --k>=0 && strcmp(shape_titles[k], shape_name) ); if( k < 0 ) ret = 1; if( !ret ) { strcpy(current_filename, shape_paths[k]); fp = fopen(current_filename, "rb"); if( !fp ) ret = 1; } if( !ret ) { fread(header, 1, 8, fp); is_png = !png_sig_cmp(header, 0, 8); if( !is_png ) ret = 1; } if( !ret ) { png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); if( !png_ptr ) ret = 1; } if( !ret ) { /* Tell libpng we already checked the first 8 bytes */ png_set_sig_bytes(png_ptr, 8); info_ptr = png_create_info_struct(png_ptr); if( !info_ptr ) ret = 1; } if( !ret ) { end_info = png_create_info_struct(png_ptr); if( !end_info ) ret = 1; } if( !ret ) { png_init_io(png_ptr, fp); png_read_info(png_ptr, info_ptr); color_type = png_get_color_type(png_ptr, info_ptr); bit_depth = png_get_bit_depth(png_ptr, info_ptr); width = png_get_image_width (png_ptr, info_ptr); height = png_get_image_height(png_ptr, info_ptr); /* Skip the alpha channel if present * stripping alpha currently doesn't work in conjunction with * converting to grayscale in libpng */ pixel_width = color_type & PNG_COLOR_MASK_ALPHA ? 2 : 1; /* Convert 16 bit data to 8 bit */ if( bit_depth == 16 ) png_set_strip_16(png_ptr); /* Expand to 1 pixel per byte if necessary */ if( bit_depth < 8 ) png_set_packing(png_ptr); /* Convert to grayscale */ if( color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA ) png_set_rgb_to_gray_fixed(png_ptr, 1, -1, -1); /* Allocate memory to hold the original png image */ image = (png_bytep*)new png_bytep[height]; for( row=0; row<(int)height; ++row ) image[row] = new png_byte[width*pixel_width]; /* Allocate memory for the pattern image that will actually be * used for the wipe */ pattern_image = new unsigned char*[frame_height]; png_read_image(png_ptr, image); png_read_end(png_ptr, end_info); double row_factor, col_factor; double row_offset = 0.5, col_offset = 0.5; // for rounding if( config.preserve_aspect && aspect_w && aspect_h ) { row_factor = (height-1)/aspect_h; col_factor = (width-1)/aspect_w; if( row_factor < col_factor ) col_factor = row_factor; else row_factor = col_factor; row_factor *= aspect_h/(double)(frame_height-1); col_factor *= aspect_w/(double)(frame_width-1); // center the pattern over the frame row_offset += (height-1-(frame_height-1)*row_factor)/2; col_offset += (width-1-(frame_width-1)*col_factor)/2; } else { // Stretch (or shrink) the pattern image to fill the frame row_factor = (double)(height-1)/(double)(frame_height-1); col_factor = (double)(width-1)/(double)(frame_width-1); } // first, determine range min..max for( int y=0; y max_value ) max_value = value; } } int range = max_value - min_value; if( !range ) range = 1; // scale to fade normalized pattern_image for( int y=0; yget_rows(); \ type **out_rows = (type**)output->get_rows(); \ for( int y=y1; y 0xff ) d = 0xff; \ else if( d < -0xff ) d = -0xff; \ tmp_type a = (d + 0xff) / 2, b = 0xff - a; \ for( int i=0; iinput = input; this->output = output; int w = input->get_w(); int h = input->get_h(); init_shapes(); load_configuration(); if( strncmp(config.shape_name, current_name, BCTEXTLEN) || config.preserve_aspect != last_preserve_aspect ) { reset_pattern_image(); } if ( !pattern_image ) { if( read_pattern_image(config.shape_name, w, h) ) { fprintf(stderr, _("Shape Wipe: cannot load shape %s\n"), current_filename); current_filename[0] = 0; return 0; } strncpy(current_name, config.shape_name, BCTEXTLEN); last_preserve_aspect = config.preserve_aspect; } float fade = (float)PluginClient::get_source_position() / (float)PluginClient::get_total_len(); if( !config.direction ) fade = 1 - fade; threshold = fade * 0xff; int slices = w*h/0x40000+1; int max_slices = BC_Resources::machine_cpus/2; if( slices > max_slices ) slices = max_slices; if( slices < 1 ) slices = 1; if( engine && engine->get_total_clients() != slices ) { delete engine; engine = 0; } if( !engine ) engine = new ShapeEngine(this, slices, slices); engine->process_packages(); return 0; } ShapePackage::ShapePackage() : LoadPackage() { } ShapeUnit::ShapeUnit(ShapeEngine *server) : LoadClient(server) { this->server = server; } ShapeUnit::~ShapeUnit() { } void ShapeUnit::process_package(LoadPackage *package) { VFrame *input = server->plugin->input; VFrame *output = server->plugin->output; int w = input->get_w(); unsigned char **pattern_image = server->plugin->pattern_image; unsigned char threshold = server->plugin->threshold; float feather = server->plugin->config.feather; ShapePackage *pkg = (ShapePackage*)package; int y1 = pkg->y1, y2 = pkg->y2; switch(input->get_color_model()) { case BC_RGB_FLOAT: SHAPEBLEND(float, 3, float) break; case BC_RGB888: case BC_YUV888: SHAPEBLEND(unsigned char, 3, int) break; case BC_RGBA_FLOAT: SHAPEBLEND(float, 4, float) break; case BC_RGBA8888: case BC_YUVA8888: SHAPEBLEND(unsigned char, 4, int) break; case BC_RGB161616: case BC_YUV161616: SHAPEBLEND(uint16_t, 3, int64_t) break; case BC_RGBA16161616: case BC_YUVA16161616: SHAPEBLEND(uint16_t, 4, int64_t) break; } } ShapeEngine::ShapeEngine(ShapeWipeMain *plugin, int total_clients, int total_packages) : LoadServer(total_clients, total_packages) { this->plugin = plugin; } ShapeEngine::~ShapeEngine() { } void ShapeEngine::init_packages() { int y = 0, h1 = plugin->input->get_h()-1; int total_packages = get_total_packages(); for(int i = 0; iy1 = y; y = h1 * i / total_packages; pkg->y2 = y; } } LoadClient* ShapeEngine::new_client() { return new ShapeUnit(this); } LoadPackage* ShapeEngine::new_package() { return new ShapePackage; }