/* * 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 "bcsignals.h" #include "bchash.h" #include "filexml.h" #include "guicast.h" #include "keyframe.h" #include "language.h" #include "pluginvclient.h" #include "transportque.inc" #include "vframe.h" #include #include #define TOP_FIELD_FIRST 0 #define BOTTOM_FIELD_FIRST 1 class FrameField; class FrameFieldWindow; class FrameFieldConfig { public: FrameFieldConfig(); int equivalent(FrameFieldConfig &src); int field_dominance; }; class FrameFieldTop : public BC_Radial { public: FrameFieldTop(FrameField *plugin, FrameFieldWindow *gui, int x, int y); int handle_event(); FrameField *plugin; FrameFieldWindow *gui; }; class FrameFieldBottom : public BC_Radial { public: FrameFieldBottom(FrameField *plugin, FrameFieldWindow *gui, int x, int y); int handle_event(); FrameField *plugin; FrameFieldWindow *gui; }; class FrameFieldDouble : public BC_CheckBox { public: FrameFieldDouble(FrameField *plugin, FrameFieldWindow *gui, int x, int y); int handle_event(); FrameField *plugin; FrameFieldWindow *gui; }; class FrameFieldShift : public BC_CheckBox { public: FrameFieldShift(FrameField *plugin, FrameFieldWindow *gui, int x, int y); int handle_event(); FrameField *plugin; FrameFieldWindow *gui; }; class FrameFieldAvg : public BC_CheckBox { public: FrameFieldAvg(FrameField *plugin, FrameFieldWindow *gui, int x, int y); int handle_event(); FrameField *plugin; FrameFieldWindow *gui; }; class FrameFieldWindow : public PluginClientWindow { public: FrameFieldWindow(FrameField *plugin); void create_objects(); FrameField *plugin; FrameFieldTop *top; FrameFieldBottom *bottom; }; class FrameField : public PluginVClient { public: FrameField(PluginServer *server); ~FrameField(); PLUGIN_CLASS_MEMBERS(FrameFieldConfig); int process_buffer(VFrame *frame, int64_t start_position, double frame_rate); int is_realtime(); void save_data(KeyFrame *keyframe); void read_data(KeyFrame *keyframe); void update_gui(); // Constructs odd or even rows from the average of the surrounding rows. void average_rows(int offset, VFrame *frame); int handle_opengl(); // Last frame requested int64_t last_frame; // Field needed int64_t field_number; // Frame needed int64_t current_frame_number; // Frame stored int64_t src_frame_number; VFrame *src_frame; // Temporary storage of input frame for OpenGL BC_Texture *src_texture; // Signal OpenGL handler a new frame was read. int new_frame; // Reading frames at a different rate requires us to store the aggregation // state when the frame isn't read. int aggregate_rgb601; int rgb601_direction; }; REGISTER_PLUGIN(FrameField) FrameFieldConfig::FrameFieldConfig() { field_dominance = TOP_FIELD_FIRST; } int FrameFieldConfig::equivalent(FrameFieldConfig &src) { return src.field_dominance == field_dominance; } FrameFieldWindow::FrameFieldWindow(FrameField *plugin) : PluginClientWindow(plugin, 210, 160, 200, 160, 0) { this->plugin = plugin; } void FrameFieldWindow::create_objects() { int x = 10, y = 10; add_subwindow(top = new FrameFieldTop(plugin, this, x, y)); y += top->get_h() + 5; add_subwindow(bottom = new FrameFieldBottom(plugin, this, x, y)); y += bottom->get_h() + 5; show_window(); flush(); } FrameFieldTop::FrameFieldTop(FrameField *plugin, FrameFieldWindow *gui, int x, int y) : BC_Radial(x, y, plugin->config.field_dominance == TOP_FIELD_FIRST, _("Top field first")) { this->plugin = plugin; this->gui = gui; } int FrameFieldTop::handle_event() { plugin->config.field_dominance = TOP_FIELD_FIRST; gui->bottom->update(0); plugin->send_configure_change(); return 1; } FrameFieldBottom::FrameFieldBottom(FrameField *plugin, FrameFieldWindow *gui, int x, int y) : BC_Radial(x, y, plugin->config.field_dominance == BOTTOM_FIELD_FIRST, _("Bottom field first")) { this->plugin = plugin; this->gui = gui; } int FrameFieldBottom::handle_event() { plugin->config.field_dominance = BOTTOM_FIELD_FIRST; gui->top->update(0); plugin->send_configure_change(); return 1; } FrameField::FrameField(PluginServer *server) : PluginVClient(server) { field_number = 0; src_frame = 0; src_frame_number = -1; last_frame = -1; src_texture = 0; aggregate_rgb601 = 0; rgb601_direction = 0; } FrameField::~FrameField() { if(src_frame) delete src_frame; if(src_texture) delete src_texture; } // 0 - current frame field 0, prev frame field 1 // 1 - current frame field 0, current frame field 1, copy current to prev // 2 - current frame field 0, prev frame field 1 int FrameField::process_buffer(VFrame *frame, int64_t start_position, double frame_rate) { load_configuration(); new_frame = 0; // Calculate current field based on absolute position so the algorithm isn't // relative to where playback started. field_number = get_source_position() % 2; if (get_direction() == PLAY_REVERSE) { start_position++; field_number = (field_number + 1) % 2; } current_frame_number = start_position / 2; VFrame *ptr = frame; if(get_use_opengl()) { // Read new frames directly into output frame for hardware } else { // Read into temporary for software if(src_frame && src_frame->get_color_model() != frame->get_color_model()) { delete src_frame; src_frame = 0; } if(!src_frame) { src_frame = new VFrame(frame->get_w(), frame->get_h(), frame->get_color_model(), 0); } ptr = src_frame; } // Import source frame at half frame rate if(current_frame_number != src_frame_number || // If same frame was requested, assume it was a configuration change and reprocess. start_position == last_frame) { read_frame(ptr, 0, current_frame_number, frame_rate / 2, get_use_opengl()); src_frame_number = current_frame_number; new_frame = 1; } if(get_use_opengl()) { run_opengl(); return 0; } int row_size = VFrame::calculate_bytes_per_pixel(frame->get_color_model()) * frame->get_w(); unsigned char **src_rows = src_frame->get_rows(); unsigned char **output_rows = frame->get_rows(); // Even field if(field_number == 0) { if(config.field_dominance == TOP_FIELD_FIRST) { for(int i = 0; i < frame->get_h() - 1; i += 2) { // Copy even lines of src to both lines of output memcpy(output_rows[i], src_rows[i], row_size); } // Average empty rows /* if(config.avg) */ average_rows(0, frame); } else { for(int i = 0; i < frame->get_h() - 1; i += 2) { // Copy odd lines of current to both lines of output with shift up. memcpy(output_rows[i + 1], src_rows[i + 1], row_size); } // Average empty rows /* if(config.avg) */ average_rows(1, frame); } } else // Odd field { if(config.field_dominance == TOP_FIELD_FIRST) { for(int i = 0; i < frame->get_h() - 1; i += 2) { // Copy odd lines of src to both lines of output memcpy(output_rows[i + 1], src_rows[i + 1], row_size); } // Average empty rows /* if(config.avg) */ average_rows(1, frame); } else { for(int i = 0; i < frame->get_h() - 1; i += 2) { // Copy even lines of src to both lines of output. memcpy(output_rows[i], src_rows[i], row_size); } // Average empty rows /* if(config.avg) */ average_rows(0, frame); } } last_frame = start_position; return 0; } // Averaging 2 pixels #define AVERAGE(type, temp_type, components, offset) \ { \ type **rows = (type**)frame->get_rows(); \ int w = frame->get_w(); \ int h = frame->get_h(); \ int row_size = components * w; \ for(int i = offset; i < h - 3; i += 2) \ { \ type *row1 = rows[i]; \ type *row2 = rows[i + 1]; \ type *row3 = rows[i + 2]; \ for(int j = 0; j < row_size; j++) \ { \ temp_type sum = (temp_type)*row1++ + (temp_type)*row3++; \ *row2++ = (sum / 2); \ } \ } \ } // Averaging 6 pixels #define AVERAGE_BAK(type, components, offset) \ { \ type **rows = (type**)frame->get_rows(); \ int w = frame->get_w(); \ int h = frame->get_h(); \ int row_size = w; \ for(int i = offset; i < h - 3; i += 2) \ { \ type *row1 = rows[i]; \ type *row2 = rows[i + 1]; \ type *row3 = rows[i + 2]; \ int64_t sum; \ int64_t pixel1[4], pixel2[4], pixel3[4]; \ int64_t pixel4[4], pixel5[4], pixel6[4]; \ \ /* First pixel */ \ for(int j = 0; j < components; j++) \ { \ pixel1[j] = *row1++; \ pixel4[j] = *row3++; \ *row2++ = (pixel1[j] + pixel4[j]) >> 1; \ } \ \ for(int j = 2; j < row_size; j++) \ { \ for(int k = 0; k < components; k++) \ { \ pixel2[k] = *row1++; \ pixel5[k] = *row3++; \ } \ \ for(int k = 0; k < components; k++) \ { \ pixel3[k] = *row1; \ pixel6[k] = *row3; \ *row2++ = (pixel1[k] + \ pixel2[k] + \ pixel3[k] + \ pixel4[k] + \ pixel5[k] + \ pixel6[k]) / 6; \ pixel1[k] = pixel2[k]; \ pixel4[k] = pixel5[k]; \ } \ } \ \ /* Last pixel */ \ for(int j = 0; j < components; j++) \ { \ *row2++ = (pixel3[j] + pixel6[j]) >> 1; \ } \ } \ } void FrameField::average_rows(int offset, VFrame *frame) { //printf("FrameField::average_rows 1 %d\n", offset); switch(frame->get_color_model()) { case BC_RGB888: case BC_YUV888: AVERAGE(unsigned char, int64_t, 3, offset); break; case BC_RGB_FLOAT: AVERAGE(float, float, 3, offset); break; case BC_RGBA8888: case BC_YUVA8888: AVERAGE(unsigned char, int64_t, 4, offset); break; case BC_RGBA_FLOAT: AVERAGE(float, float, 4, offset); break; case BC_RGB161616: case BC_YUV161616: AVERAGE(uint16_t, int64_t, 3, offset); break; case BC_RGBA16161616: case BC_YUVA16161616: AVERAGE(uint16_t, int64_t, 4, offset); break; } } const char* FrameField::plugin_title() { return N_("Frames to fields"); } int FrameField::is_realtime() { return 1; } NEW_WINDOW_MACRO(FrameField, FrameFieldWindow) int FrameField::load_configuration() { KeyFrame *prev_keyframe; FrameFieldConfig old_config = config; prev_keyframe = get_prev_keyframe(get_source_position()); read_data(prev_keyframe); return !old_config.equivalent(config); } void FrameField::save_data(KeyFrame *keyframe) { FileXML output; // cause data to be stored directly in text output.set_shared_output(keyframe->xbuf); output.tag.set_title("FRAME_FIELD"); output.tag.set_property("DOMINANCE", config.field_dominance); output.append_tag(); output.tag.set_title("/FRAME_FIELD"); output.append_tag(); output.append_newline(); output.terminate_string(); } void FrameField::read_data(KeyFrame *keyframe) { FileXML input; input.set_shared_input(keyframe->xbuf); while(!input.read_tag()) { if(input.tag.title_is("FRAME_FIELD")) { config.field_dominance = input.tag.get_property("DOMINANCE", config.field_dominance); } } } void FrameField::update_gui() { if(thread) { if(load_configuration()) { thread->window->lock_window(); ((FrameFieldWindow*)thread->window)->top->update(config.field_dominance == TOP_FIELD_FIRST); ((FrameFieldWindow*)thread->window)->bottom->update(config.field_dominance == BOTTOM_FIELD_FIRST); thread->window->unlock_window(); } } } int FrameField::handle_opengl() { #ifdef HAVE_GL static const char *field_frag = "uniform sampler2D tex;\n" "uniform float double_line_h;\n" "uniform float y_offset;\n" "void main()\n" "{\n" " vec2 coord = gl_TexCoord[0].st;\n" /* Number of double lines + fraction of current double line */ " float half_y = (coord.y - y_offset) / double_line_h;\n" /* Lines comprising current double line */ " float line1 = floor(half_y) * double_line_h + y_offset;\n" " float line2 = line1 + double_line_h;\n" /* Distance from line1 to line2 */ " float frac = fract(half_y);\n" " gl_FragColor = mix(\n" " texture2D(tex, vec2(coord.x, line1)), \n" " texture2D(tex, vec2(coord.x, line2)), \n" " frac);\n" "}\n"; static const char *_601_to_rgb_frag = "void main()\n" "{\n" " gl_FragColor.rgb = gl_FragColor.rgb * vec3(1.1644, 1.1644, 1.1644) - vec3(0.0627, 0.0627, 0.0627);\n" "}\n"; static const char *_601_to_yuv_frag = "void main()\n" "{\n" " gl_FragColor.r = gl_FragColor.r * 1.1644 - 0.0627;\n" "}\n"; static const char *rgb_to_601_frag = "void main()\n" "{\n" " gl_FragColor.rgb = gl_FragColor.rgb * vec3(0.8588, 0.8588, 0.8588) + vec3(0.0627, 0.0627, 0.0627);\n" "}\n"; static const char *yuv_to_601_frag = "void main()\n" "{\n" " gl_FragColor.r = gl_FragColor.r * 0.8588 + 0.0627;\n" "}\n"; if(new_frame) { if(get_output()->get_opengl_state() != VFrame::SCREEN) { // Copy new frame to temporary texture get_output()->to_texture(); // Draw it only to copy it to the temporary. get_output()->enable_opengl(); VFrame::init_screen(get_output()->get_w(), get_output()->get_h()); get_output()->bind_texture(0); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); get_output()->draw_texture(); } get_output()->enable_opengl(); VFrame::init_screen(get_output()->get_w(), get_output()->get_h()); glActiveTexture(GL_TEXTURE0); BC_Texture::new_texture(&src_texture, get_output()->get_w(), get_output()->get_h(), get_output()->get_color_model()); src_texture->bind(0); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, get_output()->get_w(), get_output()->get_h()); // Store aggregation state only when reading a frame //printf("FrameField::handle_opengl %p\n", get_output()); //get_output()->dump_stacks(); if(prev_effect_is(_("RGB - 601")) || next_effect_is(_("RGB - 601"))) { aggregate_rgb601 = 1; rgb601_direction = get_output()->get_params()->get("RGB601_DIRECTION", 0); } else aggregate_rgb601 = 0; } else { get_output()->enable_opengl(); } float y_offset = 0.0; if(field_number == 0) { if(config.field_dominance == BOTTOM_FIELD_FIRST) y_offset = 1.0; } else { if(config.field_dominance == TOP_FIELD_FIRST) y_offset = 1.0; } VFrame::init_screen(get_output()->get_w(), get_output()->get_h()); glActiveTexture(GL_TEXTURE0); BC_Texture::new_texture(&src_texture, get_output()->get_w(), get_output()->get_h(), get_output()->get_color_model()); const char *shader_stack[16]; memset(shader_stack,0, sizeof(shader_stack)); int current_shader = 0; shader_stack[current_shader++] = field_frag; // Aggregate with other effect //printf("FrameField::handle_opengl %s\n", get_output()->get_next_effect()); if( aggregate_rgb601 ) { switch( rgb601_direction ) { case 1: shader_stack[current_shader++] = BC_CModels::is_yuv(get_output()->get_color_model()) ? yuv_to_601_frag : rgb_to_601_frag; break; case 2: shader_stack[current_shader++] = BC_CModels::is_yuv(get_output()->get_color_model()) ? _601_to_yuv_frag : _601_to_rgb_frag; break; } } shader_stack[current_shader] = 0; unsigned int shader = VFrame::make_shader(shader_stack); if( shader > 0 ) { glUseProgram(shader); glUniform1i(glGetUniformLocation(shader, "tex"), 0); glUniform1f(glGetUniformLocation(shader, "double_line_h"), 2.0 / src_texture->get_texture_h()); glUniform1f(glGetUniformLocation(shader, "y_offset"), y_offset / src_texture->get_texture_h()); } glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); get_output()->draw_texture(); glUseProgram(0); get_output()->set_opengl_state(VFrame::SCREEN); // Reset for other operations glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); #endif return 0; }