/* * CINELERRA * Copyright (C) 2008-2016 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 "clip.h" #include "bchash.h" #include "filexml.h" #include "reframert.h" #include "guicast.h" #include "language.h" #include "pluginvclient.h" #include "theme.h" #include "transportque.h" #include REGISTER_PLUGIN(ReframeRT); ReframeRTConfig::ReframeRTConfig() { reset(); } void ReframeRTConfig::reset() { num = 1.0; denom = 1.0; stretch = 0; interp = 0; optic_flow = 1; } int ReframeRTConfig::equivalent(ReframeRTConfig &src) { return fabs(num - src.num) < 0.0001 && fabs(denom - src.denom) < 0.0001 && stretch == src.stretch && interp == src.interp; } void ReframeRTConfig::copy_from(ReframeRTConfig &src) { this->num = src.num; this->denom = src.denom; this->stretch = src.stretch; this->interp = src.interp; } void ReframeRTConfig::interpolate(ReframeRTConfig &prev, ReframeRTConfig &next, int64_t prev_frame, int64_t next_frame, int64_t current_frame) { this->interp = prev.interp; this->stretch = prev.stretch; this->denom = prev.denom; if (this->interp && prev_frame != next_frame) { double next_weight = (double)(current_frame - prev_frame) / (next_frame - prev_frame); double prev_weight = (double)(next_frame - current_frame) / (next_frame - prev_frame); double prev_slope = prev.num / prev.denom, next_slope = next.num / next.denom; // for interpolation, this is (for now) a simple linear slope to the next keyframe. double scale = prev_slope * prev_weight + next_slope * next_weight; this->num = this->denom * scale; } else { this->num = prev.num; } } void ReframeRTConfig::boundaries() { if(num < 0.0001) num = 0.0001; if(denom < 0.0001) denom = 0.0001; } ReframeRTWindow::ReframeRTWindow(ReframeRT *plugin) : PluginClientWindow(plugin, 230, 235, 230, 235, 0) { this->plugin = plugin; } ReframeRTWindow::~ReframeRTWindow() { } void ReframeRTWindow::create_objects() { int x = plugin->get_theme()->window_border; int y = plugin->get_theme()->window_border; BC_Title *title; add_subwindow(title = new BC_Title(x, y, _("Input frames:"))); y += title->get_h() + plugin->get_theme()->widget_border; num = new ReframeRTNum(plugin, this, x, y); num->create_objects(); num->set_increment(1.0); y += num->get_h() + plugin->get_theme()->widget_border; add_subwindow(title = new BC_Title(x, y, _("Output frames:"))); y += title->get_h() + plugin->get_theme()->widget_border; denom = new ReframeRTDenom(plugin, this, x, y); denom->create_objects(); denom->set_increment(1.0); y += denom->get_h() + plugin->get_theme()->widget_border; add_subwindow(stretch = new ReframeRTStretch(plugin, this, x, y)); y += 30; add_subwindow(downsample = new ReframeRTDownsample(plugin, this, x, y)); y += 30; add_subwindow(interpolate = new ReframeRTInterpolate(plugin, this, x, y)); y += 40; add_subwindow(reset = new ReframeRTReset(plugin, this, x, y)); show_window(); } void ReframeRTWindow::update() { num->update((float)plugin->config.num); denom->update((float)plugin->config.denom); stretch->update(plugin->config.stretch); downsample->update(!plugin->config.stretch); interpolate->update(plugin->config.interp); } ReframeRTNum::ReframeRTNum(ReframeRT *plugin, ReframeRTWindow *gui, int x, int y) : BC_TumbleTextBox(gui, (float)plugin->config.num, (float)0.0001, (float)1000, x, y, gui->get_w() - plugin->get_theme()->window_border * 3) { this->plugin = plugin; } int ReframeRTNum::handle_event() { plugin->config.num = atof(get_text()); plugin->config.boundaries(); plugin->send_configure_change(); return 1; } ReframeRTDenom::ReframeRTDenom(ReframeRT *plugin, ReframeRTWindow *gui, int x, int y) : BC_TumbleTextBox(gui, (float)plugin->config.denom, (float)-1000, (float)1000, x, y, gui->get_w() - plugin->get_theme()->window_border * 3) { this->plugin = plugin; } int ReframeRTDenom::handle_event() { plugin->config.denom = atof(get_text()); plugin->config.boundaries(); plugin->send_configure_change(); return 1; } ReframeRTStretch::ReframeRTStretch(ReframeRT *plugin, ReframeRTWindow *gui, int x, int y) : BC_Radial(x, y, plugin->config.stretch, _("Stretch")) { this->plugin = plugin; this->gui = gui; } int ReframeRTStretch::handle_event() { plugin->config.stretch = get_value(); gui->downsample->update(!get_value()); plugin->send_configure_change(); return 1; } ReframeRTDownsample::ReframeRTDownsample(ReframeRT *plugin, ReframeRTWindow *gui, int x, int y) : BC_Radial(x, y, !plugin->config.stretch, _("Downsample")) { this->plugin = plugin; this->gui = gui; } int ReframeRTDownsample::handle_event() { plugin->config.stretch = !get_value(); gui->stretch->update(!get_value()); plugin->send_configure_change(); return 1; } ReframeRTInterpolate::ReframeRTInterpolate(ReframeRT *plugin, ReframeRTWindow *gui, int x, int y) : BC_CheckBox(x, y, 0, _("Interpolate")) { this->plugin = plugin; this->gui = gui; } int ReframeRTInterpolate::handle_event() { plugin->config.interp = get_value(); gui->interpolate->update(get_value()); plugin->send_configure_change(); return 1; } ReframeRTReset::ReframeRTReset(ReframeRT *plugin, ReframeRTWindow *gui, int x, int y) : BC_GenericButton(x, y, _("Reset")) { this->plugin = plugin; this->gui = gui; } ReframeRTReset::~ReframeRTReset() { } int ReframeRTReset::handle_event() { plugin->config.reset(); gui->update(); plugin->send_configure_change(); return 1; } ReframeRT::ReframeRT(PluginServer *server) : PluginVClient(server) { } ReframeRT::~ReframeRT() { } const char* ReframeRT::plugin_title() { return N_("ReframeRT"); } int ReframeRT::is_realtime() { return 1; } int ReframeRT::is_synthesis() { return 1; } NEW_WINDOW_MACRO(ReframeRT, ReframeRTWindow) LOAD_CONFIGURATION_MACRO(ReframeRT, ReframeRTConfig) int ReframeRT::process_buffer(VFrame *frame, int64_t start_position, double frame_rate) { int64_t input_frame = get_source_start(); ReframeRTConfig prev_config, next_config; KeyFrame *tmp_keyframe, *next_keyframe = get_prev_keyframe(get_source_start()); int64_t tmp_position, next_position; int64_t segment_len; double input_rate = frame_rate; int is_current_keyframe; // if there are no keyframes, the default keyframe is used, and its position is always 0; // if there are keyframes, the first keyframe can be after the effect start (and it controls settings before it) // so let's calculate using a fake keyframe with the same settings but position == effect start KeyFrame *fake_keyframe = new KeyFrame(); fake_keyframe->copy_from(next_keyframe); fake_keyframe->position = local_to_edl(get_source_start()); next_keyframe = fake_keyframe; // calculate input_frame accounting for all previous keyframes do { tmp_keyframe = next_keyframe; next_keyframe = get_next_keyframe(tmp_keyframe->position+1, 0); tmp_position = edl_to_local(tmp_keyframe->position); next_position = edl_to_local(next_keyframe->position); is_current_keyframe = next_position > start_position // the next keyframe is after the current position || next_keyframe->position == tmp_keyframe->position // there are no more keyframes || !next_keyframe->position; // there are no keyframes at all if (is_current_keyframe) segment_len = start_position - tmp_position; else segment_len = next_position - tmp_position; read_data(next_keyframe); next_config.copy_from(config); read_data(tmp_keyframe); prev_config.copy_from(config); config.interpolate(prev_config, next_config, tmp_position, next_position, tmp_position + segment_len); // the area under the curve is the number of frames to advance // as long as interpolate() uses a linear slope we can use geometry to determine this // if interpolate() changes to use a curve then this needs use (possibly) the definite integral double prev_scale = prev_config.num / prev_config.denom; double config_scale = config.num / config.denom; input_frame += (int64_t)(segment_len * ((prev_scale + config_scale) / 2)); } while (!is_current_keyframe); // Change rate if (!config.stretch) { input_rate *= config.num / config.denom; } // printf("ReframeRT::process_buffer %d %lld %f %lld %f\n", // __LINE__, // start_position, // frame_rate, // input_frame, // input_rate); read_frame(frame, 0, input_frame, input_rate, 0); delete fake_keyframe; return 0; } void ReframeRT::save_data(KeyFrame *keyframe) { FileXML output; // cause data to be stored directly in text output.set_shared_output(keyframe->xbuf); output.tag.set_title("REFRAMERT"); // for backwards compatability, we call num scale output.tag.set_property("SCALE", config.num); output.tag.set_property("DENOM", config.denom); output.tag.set_property("STRETCH", config.stretch); output.tag.set_property("INTERPOLATE", config.interp); output.append_tag(); output.tag.set_title("/REFRAMERT"); output.append_tag(); output.append_newline(); output.terminate_string(); } void ReframeRT::read_data(KeyFrame *keyframe) { FileXML input; input.set_shared_input(keyframe->xbuf); while(!input.read_tag()) { if(input.tag.title_is("REFRAMERT")) { // for backwards compatability, we call num scale config.num = input.tag.get_property("SCALE", config.num); config.denom = input.tag.get_property("DENOM", config.denom); config.stretch = input.tag.get_property("STRETCH", config.stretch); config.interp = input.tag.get_property("INTERPOLATE", config.interp); } } } void ReframeRT::update_gui() { if(thread) { int changed = load_configuration(); if(changed) { ReframeRTWindow* window = (ReframeRTWindow*)thread->window; window->lock_window("ReframeRT::update_gui"); window->num->update((float)config.num); window->denom->update((float)config.denom); window->stretch->update(config.stretch); window->downsample->update(!config.stretch); window->interpolate->update(config.interp); window->unlock_window(); } } }