/* * 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 "clip.h" #include "bchash.h" #include "deinterlace-cv.h" #include "deinterwindow-cv.h" #include "filexml.h" #include "keyframe.h" #include "language.h" #include "vframe.h" #include #include #include REGISTER_PLUGIN(DeInterlaceMain) DeInterlaceConfig::DeInterlaceConfig() { mode = DEINTERLACE_AVG; dominance = 0; adaptive = 1; threshold = 40; } int DeInterlaceConfig::equivalent(DeInterlaceConfig &that) { return mode == that.mode && dominance == that.dominance && adaptive == that.adaptive && threshold == that.threshold; } void DeInterlaceConfig::copy_from(DeInterlaceConfig &that) { mode = that.mode; dominance = that.dominance; adaptive = that.adaptive; threshold = that.threshold; } void DeInterlaceConfig::interpolate(DeInterlaceConfig &prev, DeInterlaceConfig &next, int64_t prev_frame, int64_t next_frame, int64_t current_frame) { copy_from(prev); } DeInterlaceMain::DeInterlaceMain(PluginServer *server) : PluginVClient(server) { // temp = 0; temp_prevframe=0; } DeInterlaceMain::~DeInterlaceMain() { // if(temp) delete temp; if(temp_prevframe) delete temp_prevframe; } const char* DeInterlaceMain::plugin_title() { return N_("Deinterlace-CV"); } int DeInterlaceMain::is_realtime() { return 1; } #define DEINTERLACE_TOP_MACRO(type, components, dominance) \ { \ int w = input->get_w(); \ int h = input->get_h(); \ \ for(int i = 0; i < h - 1; i += 2) \ { \ type *input_row = (type*)input->get_rows()[dominance ? i + 1 : i]; \ type *output_row1 = (type*)output->get_rows()[i]; \ type *output_row2 = (type*)output->get_rows()[i + 1]; \ memcpy(output_row1, input_row, w * components * sizeof(type)); \ memcpy(output_row2, input_row, w * components * sizeof(type)); \ } \ } #define DEINTERLACE_AVG_TOP_MACRO(type, temp_type, components, dominance) \ { \ int w = input->get_w(); \ int h = input->get_h(); \ changed_rows = 0; \ \ type **in_rows = (type**)input->get_rows(); \ type **out_rows = (type**)temp->get_rows(); \ int max_h = h - 1; \ temp_type abs_diff = 0, total = 0; \ \ for(int i = 0; i < max_h; i += 2) \ { \ int in_number1 = dominance ? i - 1 : i + 0; \ int in_number2 = dominance ? i + 1 : i + 2; \ int out_number1 = dominance ? i - 1 : i; \ int out_number2 = dominance ? i : i + 1; \ in_number1 = MAX(in_number1, 0); \ in_number2 = MIN(in_number2, max_h); \ out_number1 = MAX(out_number1, 0); \ out_number2 = MIN(out_number2, max_h); \ \ type *input_row1 = in_rows[in_number1]; \ type *input_row2 = in_rows[in_number2]; \ type *input_row3 = in_rows[out_number2]; \ type *temp_row1 = out_rows[out_number1]; \ type *temp_row2 = out_rows[out_number2]; \ temp_type sum = 0; \ temp_type accum_r, accum_b, accum_g, accum_a; \ \ memcpy(temp_row1, input_row1, w * components * sizeof(type)); \ for(int j = 0; j < w; j++) \ { \ accum_r = (*input_row1++) + (*input_row2++); \ accum_g = (*input_row1++) + (*input_row2++); \ accum_b = (*input_row1++) + (*input_row2++); \ if(components == 4) \ accum_a = (*input_row1++) + (*input_row2++); \ accum_r /= 2; \ accum_g /= 2; \ accum_b /= 2; \ accum_a /= 2; \ \ total += *input_row3; \ sum = ((temp_type)*input_row3++) - accum_r; \ abs_diff += (sum < 0 ? -sum : sum); \ *temp_row2++ = accum_r; \ \ total += *input_row3; \ sum = ((temp_type)*input_row3++) - accum_g; \ abs_diff += (sum < 0 ? -sum : sum); \ *temp_row2++ = accum_g; \ \ total += *input_row3; \ sum = ((temp_type)*input_row3++) - accum_b; \ abs_diff += (sum < 0 ? -sum : sum); \ *temp_row2++ = accum_b; \ \ if(components == 4) \ { \ total += *input_row3; \ sum = ((temp_type)*input_row3++) - accum_a; \ abs_diff += (sum < 0 ? -sum : sum); \ *temp_row2++ = accum_a; \ } \ } \ } \ \ temp_type threshold = (temp_type)total * config.threshold / THRESHOLD_SCALAR; \ /* printf("total=%lld threshold=%lld abs_diff=%lld\n", total, threshold, abs_diff); */ \ if(abs_diff > threshold || !config.adaptive) \ { \ output->copy_from(temp); \ changed_rows = 240; \ } \ else \ { \ output->copy_from(input); \ changed_rows = 0; \ } \ \ } #define DEINTERLACE_AVG_MACRO(type, temp_type, components) \ { \ int w = input->get_w(); \ int h = input->get_h(); \ \ for(int i = 0; i < h - 1; i += 2) \ { \ type *input_row1 = (type*)input->get_rows()[i]; \ type *input_row2 = (type*)input->get_rows()[i + 1]; \ type *output_row1 = (type*)output->get_rows()[i]; \ type *output_row2 = (type*)output->get_rows()[i + 1]; \ type result; \ \ for(int j = 0; j < w * components; j++) \ { \ result = ((temp_type)input_row1[j] + input_row2[j]) / 2; \ output_row1[j] = result; \ output_row2[j] = result; \ } \ } \ } #define DEINTERLACE_SWAP_MACRO(type, components, dominance) \ { \ int w = input->get_w(); \ int h = input->get_h(); \ \ for(int i = dominance; i < h - 1; i += 2) \ { \ type *input_row1 = (type*)input->get_rows()[i]; \ type *input_row2 = (type*)input->get_rows()[i + 1]; \ type *output_row1 = (type*)output->get_rows()[i]; \ type *output_row2 = (type*)output->get_rows()[i + 1]; \ type temp1, temp2; \ \ for(int j = 0; j < w * components; j++) \ { \ temp1 = input_row1[j]; \ temp2 = input_row2[j]; \ output_row1[j] = temp2; \ output_row2[j] = temp1; \ } \ } \ } #define DEINTERLACE_TEMPORALSWAP_MACRO(type, components, dominance) \ { \ int w = input->get_w(); \ int h = input->get_h(); \ \ for(int i = 0; i < h - 1; i += 2) \ { \ type *input_row1;\ type *input_row2; \ type *output_row1 = (type*)output->get_rows()[i]; \ type *output_row2 = (type*)output->get_rows()[i + 1]; \ type temp1, temp2; \ \ if (dominance) { \ input_row1 = (type*)input->get_rows()[i]; \ input_row2 = (type*)prevframe->get_rows()[i+1]; \ } \ else {\ input_row1 = (type*)prevframe->get_rows()[i]; \ input_row2 = (type*)input->get_rows()[i+1]; \ } \ \ for(int j = 0; j < w * components; j++) \ { \ temp1 = input_row1[j]; \ temp2 = input_row2[j]; \ output_row1[j] = temp1; \ output_row2[j] = temp2; \ } \ } \ } /* Bob & Weave deinterlacer: For each pixel, if it's similar to the previous frame then keep it else average with line above and below Similar is defined as in abs(difference)/(sum) < threshold */ // cplr warns, #define FABS(a) (((a)<0)?(0-(a)):(a)) static inline int FABS(int8_t v) { return v<0 ? -v : v; } static inline int FABS(uint8_t v) { return v; } static inline int FABS(int16_t v) { return v<0 ? -v : v; } static inline int FABS(uint16_t v) { return v; } static inline int FABS(int32_t v) { return v<0 ? -v : v; } static inline int64_t FABS(uint32_t v) { return v; } static inline int64_t FABS(int64_t v) { return v<0 ? -v : v; } static inline int64_t FABS(uint64_t v) { return v; } static inline float FABS(float v) { return v<0 ? -v : v; } static inline double FABS(double v) { return v<0 ? -v : v; } #define FMAX(a,b) (((a)>(b))?(a):(b)) #define FMIN(a,b) (((a)<(b))?(a):(b)) #define SQ(a) ((a)*(a)) // threshold < 100 -> a-b/a+b < #define DEINTERLACE_BOBWEAVE_MACRO(type, temp_type, components, dominance, threshold, noise_threshold) \ { \ /* Ooooohh, I like fudge factors */ \ double exp_threshold=exp(((double)threshold - 50 )/2);\ int w = input->get_w(); \ int h = input->get_h(); \ type *row_above=(type*)input->get_rows()[0]; \ for(int i = dominance ?0:1; i < h - 1; i += 2) \ { \ type *input_row;\ type *input_row2; \ type *old_row; \ type *output_row1 = (type*)output->get_rows()[i]; \ type *output_row2 = (type*)output->get_rows()[i + 1]; \ temp_type pixel, below, old, above; \ \ input_row = (type*)input->get_rows()[i]; \ input_row2 = (type*)input->get_rows()[i+1]; \ old_row = (type*)prevframe->get_rows()[i]; \ \ for(int j = 0; j < w * components; j++) \ { \ pixel = input_row[j]; \ below = input_row2[j]; \ old = old_row[j]; \ above = row_above[j]; \ \ if ( ( FABS(pixel-old) <= noise_threshold ) \ || ((pixel+old != 0) && (((FABS((double) pixel-old))/((double) pixel+old)) >= exp_threshold )) \ || ((above+below != 0) && (((FABS((double) pixel-old))/((double) above+below)) >= exp_threshold )) \ ) {\ pixel=(above+below)/2 ;\ }\ output_row1[j] = pixel; \ output_row2[j] = below; \ } \ row_above=input_row2; \ } \ } void DeInterlaceMain::deinterlace_top(VFrame *input, VFrame *output, int dominance) { switch(input->get_color_model()) { case BC_RGB888: case BC_YUV888: DEINTERLACE_TOP_MACRO(unsigned char, 3, dominance); break; case BC_RGB_FLOAT: DEINTERLACE_TOP_MACRO(float, 3, dominance); break; case BC_RGBA8888: case BC_YUVA8888: DEINTERLACE_TOP_MACRO(unsigned char, 4, dominance); break; case BC_RGBA_FLOAT: DEINTERLACE_TOP_MACRO(float, 4, dominance); break; case BC_RGB161616: case BC_YUV161616: DEINTERLACE_TOP_MACRO(uint16_t, 3, dominance); break; case BC_RGBA16161616: case BC_YUVA16161616: DEINTERLACE_TOP_MACRO(uint16_t, 4, dominance); break; } } void DeInterlaceMain::deinterlace_avg_top(VFrame *input, VFrame *output, int dominance) { switch(input->get_color_model()) { case BC_RGB888: case BC_YUV888: DEINTERLACE_AVG_TOP_MACRO(unsigned char, int64_t, 3, dominance); break; case BC_RGB_FLOAT: DEINTERLACE_AVG_TOP_MACRO(float, double, 3, dominance); break; case BC_RGBA8888: case BC_YUVA8888: DEINTERLACE_AVG_TOP_MACRO(unsigned char, int64_t, 4, dominance); break; case BC_RGBA_FLOAT: DEINTERLACE_AVG_TOP_MACRO(float, double, 4, dominance); break; case BC_RGB161616: case BC_YUV161616: DEINTERLACE_AVG_TOP_MACRO(uint16_t, int64_t, 3, dominance); break; case BC_RGBA16161616: case BC_YUVA16161616: DEINTERLACE_AVG_TOP_MACRO(uint16_t, int64_t, 4, dominance); break; } } void DeInterlaceMain::deinterlace_avg(VFrame *input, VFrame *output) { switch(input->get_color_model()) { case BC_RGB888: case BC_YUV888: DEINTERLACE_AVG_MACRO(unsigned char, uint64_t, 3); break; case BC_RGB_FLOAT: DEINTERLACE_AVG_MACRO(float, double, 3); break; case BC_RGBA8888: case BC_YUVA8888: DEINTERLACE_AVG_MACRO(unsigned char, uint64_t, 4); break; case BC_RGBA_FLOAT: DEINTERLACE_AVG_MACRO(float, double, 4); break; case BC_RGB161616: case BC_YUV161616: DEINTERLACE_AVG_MACRO(uint16_t, uint64_t, 3); break; case BC_RGBA16161616: case BC_YUVA16161616: DEINTERLACE_AVG_MACRO(uint16_t, uint64_t, 4); break; } } void DeInterlaceMain::deinterlace_swap(VFrame *input, VFrame *output, int dominance) { switch(input->get_color_model()) { case BC_RGB888: case BC_YUV888: DEINTERLACE_SWAP_MACRO(unsigned char, 3, dominance); break; case BC_RGB_FLOAT: DEINTERLACE_SWAP_MACRO(float, 3, dominance); break; case BC_RGBA8888: case BC_YUVA8888: DEINTERLACE_SWAP_MACRO(unsigned char, 4, dominance); break; case BC_RGBA_FLOAT: DEINTERLACE_SWAP_MACRO(float, 4, dominance); break; case BC_RGB161616: case BC_YUV161616: DEINTERLACE_SWAP_MACRO(uint16_t, 3, dominance); break; case BC_RGBA16161616: case BC_YUVA16161616: DEINTERLACE_SWAP_MACRO(uint16_t, 4, dominance); break; } } void DeInterlaceMain::deinterlace_temporalswap(VFrame *input, VFrame *prevframe, VFrame *output, int dominance) { switch(input->get_color_model()) { case BC_RGB888: case BC_YUV888: DEINTERLACE_TEMPORALSWAP_MACRO(unsigned char, 3, dominance); break; case BC_RGB_FLOAT: DEINTERLACE_TEMPORALSWAP_MACRO(float, 3, dominance); break; case BC_RGBA8888: case BC_YUVA8888: DEINTERLACE_TEMPORALSWAP_MACRO(unsigned char, 4, dominance); break; case BC_RGBA_FLOAT: DEINTERLACE_TEMPORALSWAP_MACRO(float, 4, dominance); break; case BC_RGB161616: case BC_YUV161616: DEINTERLACE_TEMPORALSWAP_MACRO(uint16_t, 3, dominance); break; case BC_RGBA16161616: case BC_YUVA16161616: DEINTERLACE_TEMPORALSWAP_MACRO(uint16_t, 4, dominance); break; } } void DeInterlaceMain::deinterlace_bobweave(VFrame *input, VFrame *prevframe, VFrame *output, int dominance) { int threshold=config.threshold; int noise_threshold=0; switch(input->get_color_model()) { case BC_RGB888: case BC_YUV888: DEINTERLACE_BOBWEAVE_MACRO(unsigned char, uint64_t, 3, dominance, threshold, noise_threshold); break; case BC_RGB_FLOAT: DEINTERLACE_BOBWEAVE_MACRO(float, double, 3, dominance, threshold, noise_threshold); break; case BC_RGBA8888: case BC_YUVA8888: DEINTERLACE_BOBWEAVE_MACRO(unsigned char, uint64_t, 4, dominance, threshold, noise_threshold); break; case BC_RGBA_FLOAT: DEINTERLACE_BOBWEAVE_MACRO(float, double, 4, dominance, threshold, noise_threshold); break; case BC_RGB161616: case BC_YUV161616: DEINTERLACE_BOBWEAVE_MACRO(uint16_t, uint64_t, 3, dominance, threshold, noise_threshold); break; case BC_RGBA16161616: case BC_YUVA16161616: DEINTERLACE_BOBWEAVE_MACRO(uint16_t, uint64_t, 4, dominance, threshold, noise_threshold); break; } } int DeInterlaceMain::process_buffer(VFrame *frame, int64_t start_position, double frame_rate) { changed_rows = frame->get_h(); load_configuration(); read_frame(frame, 0, start_position, frame_rate, 0); // Temp was used for adaptive deinterlacing where it took deinterlacing // an entire frame to decide if the deinterlaced output should be used. temp = frame; // if(!temp) // temp = new VFrame(frame->get_w(), frame->get_h(), // frame->get_color_model(), 0); if(!temp_prevframe) temp_prevframe = new VFrame(frame->get_w(), frame->get_h(), frame->get_color_model(), 0); switch(config.mode) { case DEINTERLACE_NONE: // output->copy_from(input); break; case DEINTERLACE_KEEP: deinterlace_top(frame, frame, config.dominance); break; case DEINTERLACE_AVG: deinterlace_avg(frame, frame); break; case DEINTERLACE_AVG_1F: deinterlace_avg_top(frame, frame, config.dominance); break; case DEINTERLACE_SWAP: deinterlace_swap(frame, frame, config.dominance); break; case DEINTERLACE_BOBWEAVE: if (get_source_position()==0) read_frame(temp_prevframe,0, get_source_position(), get_framerate(), 0); else read_frame(temp_prevframe,0, get_source_position()-1, get_framerate(), 0); deinterlace_bobweave(frame, temp_prevframe, frame, config.dominance); break; case DEINTERLACE_TEMPORALSWAP: if (get_source_position()==0) read_frame(temp_prevframe,0, get_source_position(), get_framerate(), 0); else read_frame(temp_prevframe,0, get_source_position()-1, get_framerate(), 0); deinterlace_temporalswap(frame, temp_prevframe, frame, config.dominance); break; } send_render_gui(&changed_rows); return 0; } void DeInterlaceMain::render_gui(void *data) { if(thread) { thread->window->lock_window(); char string[BCTEXTLEN]; DeInterlaceWindow *window = (DeInterlaceWindow *)thread->window; window->get_status_string(string, *(int*)data); window->status->update(string); window->flush(); window->unlock_window(); } } NEW_WINDOW_MACRO(DeInterlaceMain, DeInterlaceWindow) LOAD_CONFIGURATION_MACRO(DeInterlaceMain, DeInterlaceConfig) void DeInterlaceMain::save_data(KeyFrame *keyframe) { FileXML output; output.set_shared_output(keyframe->xbuf); output.tag.set_title("DEINTERLACE"); output.tag.set_property("MODE", config.mode); output.tag.set_property("DOMINANCE", config.dominance); output.tag.set_property("ADAPTIVE", config.adaptive); output.tag.set_property("THRESHOLD", config.threshold); output.append_tag(); output.tag.set_title("/DEINTERLACE"); output.append_tag(); output.terminate_string(); } void DeInterlaceMain::read_data(KeyFrame *keyframe) { FileXML input; input.set_shared_input(keyframe->xbuf); while(!input.read_tag()) { if(input.tag.title_is("DEINTERLACE")) { config.mode = input.tag.get_property("MODE", config.mode); config.dominance = input.tag.get_property("DOMINANCE", config.dominance); config.adaptive = input.tag.get_property("ADAPTIVE", config.adaptive); config.threshold = input.tag.get_property("THRESHOLD", config.threshold); } } } void DeInterlaceMain::update_gui() { if(thread) { load_configuration(); DeInterlaceWindow *window = (DeInterlaceWindow *)thread->window; window->lock_window(); window->set_mode(config.mode, 1); if (window->dominance_top) window->dominance_top->update(config.dominance?0:BC_Toggle::TOGGLE_CHECKED); if (window->dominance_bottom) window->dominance_bottom->update(config.dominance?BC_Toggle::TOGGLE_CHECKED:0); if (window->adaptive) window->adaptive->update(config.adaptive); if (window->threshold) window->threshold->update(config.threshold); window->unlock_window(); } }