/* DeScratch - Scratches Removing Filter Plugin for Avisynth 2.5 Copyright (c)2003-2016 Alexander G. Balakhnin aka Fizick bag@hotmail.ru http://avisynth.org.ru This program is FREE software under GPL licence v2. This plugin removes vertical scratches from digitized films. Reworked for cin5 by GG. 03/2018, from the laws of Fizick's */ #include "clip.h" #include "filexml.h" #include "language.h" #include "descratch.h" REGISTER_PLUGIN(DeScratchMain) DeScratchMain::DeScratchMain(PluginServer *server) : PluginVClient(server) { inf = 0; sz_inf = 0; src = 0; dst = 0; tmp_frame = 0; blurry = 0; overlay_frame = 0; } DeScratchMain::~DeScratchMain() { delete [] inf; delete src; delete dst; delete blurry; delete tmp_frame; delete overlay_frame; } const char* DeScratchMain::plugin_title() { return N_("DeScratch"); } int DeScratchMain::is_realtime() { return 1; } void DeScratchConfig::reset() { threshold = 24; asymmetry = 16; min_width = 1; max_width = 3; min_len = 1; max_len = 100; max_angle = 45; blur_len = 4; gap_len = 10; mode_y = MODE_ALL; mode_u = MODE_NONE; mode_v = MODE_NONE; mark = 0; ffade = 100; border = 2; } DeScratchConfig::DeScratchConfig() { reset(); } DeScratchConfig::~DeScratchConfig() { } int DeScratchConfig::equivalent(DeScratchConfig &that) { return threshold == that.threshold && asymmetry == that.asymmetry && min_width == that.min_width && max_width == that.max_width && min_len == that.min_len && max_len == that.max_len && max_angle == that.max_angle && blur_len == that.blur_len && gap_len == that.gap_len && mode_y == that.mode_y && mode_u == that.mode_u && mode_v == that.mode_v && mark == that.mark && ffade == that.ffade && border == that.border; } void DeScratchConfig::copy_from(DeScratchConfig &that) { threshold = that.threshold; asymmetry = that.asymmetry; min_width = that.min_width; max_width = that.max_width; min_len = that.min_len; max_len = that.max_len; max_angle = that.max_angle; blur_len = that.blur_len; gap_len = that.gap_len; mode_y = that.mode_y; mode_u = that.mode_u; mode_v = that.mode_v; mark = that.mark; ffade = that.ffade; border = that.border; } void DeScratchConfig::interpolate(DeScratchConfig &prev, DeScratchConfig &next, int64_t prev_frame, int64_t next_frame, int64_t current_frame) { copy_from(prev); } LOAD_CONFIGURATION_MACRO(DeScratchMain, DeScratchConfig) void DeScratchMain::save_data(KeyFrame *keyframe) { FileXML output; // cause data to be stored directly in text output.set_shared_output(keyframe->get_data(), MESSAGESIZE); // Store data output.tag.set_title("DESCRATCH"); output.tag.set_property("THRESHOLD", config.threshold); output.tag.set_property("ASYMMETRY", config.asymmetry); output.tag.set_property("MIN_WIDTH", config.min_width); output.tag.set_property("MAX_WIDTH", config.max_width); output.tag.set_property("MIN_LEN", config.min_len); output.tag.set_property("MAX_LEN", config.max_len); output.tag.set_property("MAX_ANGLE", config.max_angle); output.tag.set_property("BLUR_LEN", config.blur_len); output.tag.set_property("GAP_LEN", config.gap_len); output.tag.set_property("MODE_Y", config.mode_y); output.tag.set_property("MODE_U", config.mode_u); output.tag.set_property("MODE_V", config.mode_v); output.tag.set_property("MARK", config.mark); output.tag.set_property("FFADE", config.ffade); output.tag.set_property("BORDER", config.border); output.append_tag(); output.tag.set_title("/DESCRATCH"); output.append_tag(); output.append_newline(); output.terminate_string(); } void DeScratchMain::read_data(KeyFrame *keyframe) { FileXML input; input.set_shared_input(keyframe->get_data(), strlen(keyframe->get_data())); int result = 0; while( !(result = input.read_tag()) ) { if(input.tag.title_is("DESCRATCH")) { config.threshold = input.tag.get_property("THRESHOLD", config.threshold); config.asymmetry = input.tag.get_property("ASYMMETRY", config.asymmetry); config.min_width = input.tag.get_property("MIN_WIDTH", config.min_width); config.max_width = input.tag.get_property("MAX_WIDTH", config.max_width); config.min_len = input.tag.get_property("MIN_LEN", config.min_len); config.max_len = input.tag.get_property("MAX_LEN", config.max_len); config.max_angle = input.tag.get_property("MAX_ANGLE", config.max_angle); config.blur_len = input.tag.get_property("BLUR_LEN", config.blur_len); config.gap_len = input.tag.get_property("GAP_LEN", config.gap_len); config.mode_y = input.tag.get_property("MODE_Y", config.mode_y); config.mode_u = input.tag.get_property("MODE_U", config.mode_u); config.mode_v = input.tag.get_property("MODE_V", config.mode_v); config.mark = input.tag.get_property("MARK", config.mark); config.ffade = input.tag.get_property("FFADE", config.ffade); config.border = input.tag.get_property("BORDER", config.border); } } } void DeScratchMain::get_extrems_plane(int comp, int thresh) { uint8_t **rows = blurry->get_rows(); int d = config.max_width, d1 = d+1, wd = src_w - d1; int bpp = 3, dsz = d * bpp; int asym = config.asymmetry; if( thresh > 0 ) { // black (low value) scratches for( int y=0; y thresh && (rp[0]-*dp) > thresh && (abs(lp[-bpp]-rp[+bpp]) <= asym) && ((lp[0]-lp[+bpp]) + (rp[0]-rp[-bpp]) > (lp[-bpp]-lp[0]) + (rp[bpp]-rp[0])) ? SD_EXTREM : SD_NULL; // sharp extremum found } for( ; xget_rows(); int d = config.min_width, d1 = d+1, wd = src_w - d1; int bpp = 3, dsz = d * bpp; int asym = config.asymmetry; if( thresh > 0 ) { // black (low value) scratches for( int y=0; y thresh && (rp[0]-*dp) > thresh && (abs(lp[-bpp]-rp[+bpp]) <= asym) && ((lp[0]-lp[+bpp]) + (rp[0]-rp[-bpp]) > (lp[-bpp]-lp[0]) + (rp[bpp]-rp[0])) ) *ip = SD_NULL; // sharp extremum found } } } else { // white (high value) scratches for( int y=0; y0; ) *(bp-=src_w) = SD_EXTREM; } } } void DeScratchMain::test_scratches() { int w2 = src_w - 2; int min_len = config.min_len * src_h / 100; int max_len = config.max_len * src_h / 100; int maxwidth = config.max_width*2 + 1; int maxangle = config.max_angle; for( int y=0; y= 3 ) { if( ip[-2] == SD_EXTREM ) { ip[-2] = SD_TESTED; nctr = ctr-2; ++n; } if( ip[+2] == SD_EXTREM ) { ip[+2] = SD_TESTED; nctr = ctr+2; ++n; } } if( ip[-1] == SD_EXTREM ) { ip[-1] = SD_TESTED; nctr = ctr-1; ++n; } if( ip[+1] == SD_EXTREM ) { ip[+1] = SD_TESTED; nctr = ctr+1; ++n; } if( ip[+0] == SD_EXTREM ) { ip[+0] = SD_TESTED; nctr = ctr+0; ++n; } // end of points tests, check result for row: // check gap and angle, if no points or big angle, it is end of inf if( !n || abs(nctr%src_w - x) >= maxwidth+len*maxangle/57 ) break; ctr = nctr + src_w; // new center for next row test } int mask = len >= min_len && len <= max_len ? SD_GOOD : SD_REJECT; ctr = ofs+1; nctr = ctr; // pass2 for( len=0; len= 3 ) { if( ip[-2] == SD_TESTED ) { ip[-2] = mask; nctr = ctr-2; ++n; } if( ip[+2] == SD_TESTED ) { ip[+2] = mask; nctr = ctr+2; ++n; } } if( ip[-1] == SD_TESTED ) { ip[-1] = mask; nctr = ctr-1; ++n; } if( ip[+1] == SD_TESTED ) { ip[+1] = mask; nctr = ctr+1; ++n; } if( ip[+0] == SD_TESTED ) { ip[+0] = mask; nctr = ctr+0; ++n; } // end of points tests, check result for row: // check gap and angle, if no points or big angle, it is end of inf if( !n || abs(nctr%src_w - x) >= maxwidth+len*maxangle/57 ) break; ctr = nctr + src_w; // new center for next row test } } } } void DeScratchMain::mark_scratches_plane(int comp, int mask, int value) { int bpp = 3, dst_w = dst->get_w(), dst_h = dst->get_h(); uint8_t **rows = dst->get_rows(); for( int y=0; yget_rows(); uint8_t **dst_rows = dst->get_rows(); uint8_t **blur_rows = blurry->get_rows(); int bpp = 3, margin = r+config.border+2, wm = src_w-margin; float nrm = 1. / 1024.f, nrm2r = nrm / (2*r*bpp); for( int y=0; yrs; i-=bpp ) { // at right border int rv = inp[i] + blur[rt] - blur[i]; int v = nrm*(fade*rv + fade1*inp[rt]); out[i] = CLIP(v,0,255); } left = 0; } } ip += src_w; } } void DeScratchMain::pass(int comp, int thresh) { // pass for current plane and current sign get_extrems_plane(comp, thresh); if( config.min_width > 1 ) remove_min_extrems_plane(comp, thresh); close_gaps(); test_scratches(); if( config.mark ) { int value = config.threshold > 0 ? 0 : 255; mark_scratches_plane(comp, SD_GOOD, value); mark_scratches_plane(comp, SD_REJECT, 127); } else remove_scratches_plane(comp); } void DeScratchMain::blur(int scale) { int tw = src_w, th = (src_h / scale) & ~1; if( tmp_frame && (tmp_frame->get_w() != tw || tmp_frame->get_h() != th) ) { delete tmp_frame; tmp_frame = 0; } if( !tmp_frame ) tmp_frame = new VFrame(tw, th, BC_YUV888); if( blurry && (blurry->get_w() != src_w || blurry->get_h() != src_h) ) { delete blurry; blurry = 0; } if( !blurry ) blurry = new VFrame(src_w, src_h, BC_YUV888); overlay_frame->overlay(tmp_frame, src, 0,0,src_w,src_h, 0,0,tw,th, 1.f, TRANSFER_NORMAL, LINEAR_LINEAR); overlay_frame->overlay(blurry, tmp_frame, 0,0,tw,th, 0,0,src_w,src_h, 1.f, TRANSFER_NORMAL, CUBIC_CUBIC); } void DeScratchMain::copy(int comp) { uint8_t **src_rows = src->get_rows(); uint8_t **dst_rows = dst->get_rows(); for( int y=0; yget_w(); src_h = input->get_h(); if( src_w >= 2*config.max_width+3 ) { if( !overlay_frame ) { int cpus = PluginClient::smp + 1; if( cpus > 8 ) cpus = 8; overlay_frame = new OverlayFrame(cpus); } if( src && (src->get_w() != src_w || src->get_h() != src_h) ) { delete src; src = 0; } if( !src ) src = new VFrame(src_w, src_h, BC_YUV888); src->transfer_from(input); if( dst && (dst->get_w() != src_w || dst->get_h() != src_h) ) { delete dst; dst = 0; } if( !dst ) dst = new VFrame(src_w, src_h, BC_YUV888); dst->copy_from(src); int sz = src_w * src_h; if( sz_inf != sz ) { delete [] inf; inf = 0; } if( !inf ) inf = new uint8_t[sz_inf=sz]; blur(config.blur_len + 1); plane_pass(0, config.mode_y); plane_pass(1, config.mode_u); plane_pass(2, config.mode_v); output->transfer_from(dst); } return 0; } void DeScratchMain::update_gui() { if( !thread ) return; DeScratchWindow *window = (DeScratchWindow *)thread->get_window(); window->lock_window("DeScratchMain::update_gui"); if( load_configuration() ) window->update_gui(); window->unlock_window(); } NEW_WINDOW_MACRO(DeScratchMain, DeScratchWindow) DeScratchWindow::DeScratchWindow(DeScratchMain *plugin) : PluginClientWindow(plugin, 512, 256, 512, 256, 0) { this->plugin = plugin; } DeScratchWindow::~DeScratchWindow() { } void DeScratchWindow::create_objects() { int x = 10, y = 10; plugin->load_configuration(); DeScratchConfig &config = plugin->config; BC_Title *title; add_tool(title = new BC_Title(x, y, _("DeScratch:"))); y += title->get_h() + 5; int x1 = x, x2 = get_w()/2; add_tool(title = new BC_Title(x1=x, y, _("threshold:"))); x1 += title->get_w()+16; add_tool(threshold = new DeScratchISlider(this, x1, y, x2-x1-10, 0,64, &config.threshold)); add_tool(title = new BC_Title(x1=x2, y, _("asymmetry:"))); x1 += title->get_w()+16; add_tool(asymmetry = new DeScratchISlider(this, x1, y, get_w()-x1-15, 0,64, &config.asymmetry)); y += threshold->get_h() + 10; add_tool(title = new BC_Title(x1=x, y, _("Mode:"))); x1 += title->get_w()+16; add_tool(title = new BC_Title(x1, y, _("y:"))); int w1 = title->get_w()+16; add_tool(y_mode = new DeScratchMode(this, (x1+=w1), y, &config.mode_y)); y_mode->create_objects(); x1 += y_mode->get_w()+16; add_tool(title = new BC_Title(x1, y, _("u:"))); add_tool(u_mode = new DeScratchMode(this, (x1+=w1), y, &config.mode_u)); u_mode->create_objects(); x1 += u_mode->get_w()+16; add_tool(title = new BC_Title(x1, y, _("v:"))); add_tool(v_mode = new DeScratchMode(this, (x1+=w1), y, &config.mode_v)); v_mode->create_objects(); y += y_mode->get_h() + 10; add_tool(title = new BC_Title(x1=x, y, _("width:"))); w1 = title->get_w()+16; x1 += w1; add_tool(title = new BC_Title(x1, y, _("min:"))); x1 += title->get_w()+16; add_tool(min_width = new DeScratchISlider(this, x1, y, x2-x1-10, 0,16, &config.min_width)); add_tool(title = new BC_Title(x1=x2, y, _("max:"))); x1 += title->get_w()+16; add_tool(max_width = new DeScratchISlider(this, x1, y, get_w()-x1-15, 0,16, &config.max_width)); y += min_width->get_h() + 10; add_tool(title = new BC_Title(x1=x, y, _("len:"))); w1 = title->get_w()+16; x1 += w1; add_tool(title = new BC_Title(x1, y, _("min:"))); x1 += title->get_w()+16; add_tool(min_len = new DeScratchFSlider(this, x1, y, x2-x1-10, 0.0,100.0, &config.min_len)); add_tool(title = new BC_Title(x1=x2, y, _("max:"))); x1 += title->get_w()+16; add_tool(max_len = new DeScratchFSlider(this, x1, y, get_w()-x1-15, 0.0,100.0, &config.max_len)); y += min_len->get_h() + 10; add_tool(title = new BC_Title(x1=x, y, _("len:"))); w1 = title->get_w()+16; x1 += w1; add_tool(title = new BC_Title(x1, y, _("blur:"))); x1 += title->get_w()+16; add_tool(blur_len = new DeScratchISlider(this, x1, y, x2-x1-10, 0,16, &config.blur_len)); add_tool(title = new BC_Title(x1=x2, y, _("gap:"))); x1 += title->get_w()+16; add_tool(gap_len = new DeScratchFSlider(this, x1, y, get_w()-x1-15, 0.0,100.0, &config.gap_len)); y += blur_len->get_h() + 10; add_tool(title = new BC_Title(x1=x, y, _("max angle:"))); w1 = title->get_w()+16; x1 += w1; add_tool(max_angle = new DeScratchFSlider(this, x1, y, x2-x1-10, 0.0,90.0, &config.max_angle)); add_tool(title = new BC_Title(x1=x2, y, _("fade:"))); x1 += title->get_w()+16; add_tool(ffade = new DeScratchFSlider(this, x1, y, get_w()-x1-15, 0.0,100.0, &config.ffade)); y += max_angle->get_h() + 10; add_tool(title = new BC_Title(x1=x, y, _("border:"))); x1 += title->get_w()+16; add_tool(border = new DeScratchISlider(this, x1, y, x2-x1-10, 0,16, &config.border)); add_tool(mark = new DeScratchMark(this, x1=x2, y)); w1 = DeScratchReset::calculate_w(this, _("Reset")); add_tool(reset = new DeScratchReset(this, get_w()-w1-15, y)); show_window(); } void DeScratchWindow::update_gui() { DeScratchConfig &config = plugin->config; y_mode->update(config.mode_y); u_mode->update(config.mode_u); v_mode->update(config.mode_v); min_width->update(config.min_width); max_width->update(config.max_width); min_len->update(config.min_len); max_len->update(config.max_len); blur_len->update(config.blur_len); gap_len->update(config.gap_len); max_angle->update(config.max_angle); ffade->update(config.ffade); mark->update(config.mark); } DeScratchModeItem::DeScratchModeItem(DeScratchMode *popup, int type, const char *text) : BC_MenuItem(text) { this->popup = popup; this->type = type; } DeScratchModeItem::~DeScratchModeItem() { } int DeScratchModeItem::handle_event() { popup->update(type); return popup->handle_event(); } DeScratchMode::DeScratchMode(DeScratchWindow *win, int x, int y, int *value) : BC_PopupMenu(x, y, 64, "", 1) { this->win = win; this->value = value; } DeScratchMode::~DeScratchMode() { } void DeScratchMode::create_objects() { add_item(new DeScratchModeItem(this, MODE_NONE, _("None"))); add_item(new DeScratchModeItem(this, MODE_LOW, _("Low"))); add_item(new DeScratchModeItem(this, MODE_HIGH, _("High"))); add_item(new DeScratchModeItem(this, MODE_ALL, _("All"))); set_value(*value); } int DeScratchMode::handle_event() { win->plugin->send_configure_change(); return 1; } void DeScratchMode::update(int v) { set_value(*value = v); } void DeScratchMode::set_value(int v) { int i = total_items(); while( --i >= 0 && ((DeScratchModeItem*)get_item(i))->type != v ); if( i >= 0 ) set_text(get_item(i)->get_text()); } DeScratchISlider::DeScratchISlider(DeScratchWindow *win, int x, int y, int w, int min, int max, int *output) : BC_ISlider(x, y, 0, w, w, min, max, *output) { this->win = win; this->output = output; } DeScratchISlider::~DeScratchISlider() { } int DeScratchISlider::handle_event() { *output = get_value(); win->plugin->send_configure_change(); return 1; } DeScratchFSlider::DeScratchFSlider(DeScratchWindow *win, int x, int y, int w, float min, float max, float *output) : BC_FSlider(x, y, 0, w, w, min, max, *output) { this->win = win; this->output = output; } DeScratchFSlider::~DeScratchFSlider() { } int DeScratchFSlider::handle_event() { *output = get_value(); win->plugin->send_configure_change(); return 1; } DeScratchMark::DeScratchMark(DeScratchWindow *win, int x, int y) : BC_CheckBox(x, y, &win->plugin->config.mark, _("Mark")) { this->win = win; }; DeScratchMark::~DeScratchMark() { } int DeScratchMark::handle_event() { int ret = BC_CheckBox::handle_event(); win->plugin->send_configure_change(); return ret; } DeScratchReset::DeScratchReset(DeScratchWindow *win, int x, int y) : BC_GenericButton(x, y, _("Reset")) { this->win = win; } int DeScratchReset::handle_event() { win->plugin->config.reset(); win->update_gui(); win->plugin->send_configure_change(); return 1; }