/* * 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 "clip.h" #include "bchash.h" #include "bcsignals.h" #include "filexml.h" #include "keyframe.h" #include "language.h" #include "unsharp.h" #include "unsharpwindow.h" #include #include REGISTER_PLUGIN(UnsharpMain) UnsharpConfig::UnsharpConfig() { reset(RESET_DEFAULT_SETTINGS); } void UnsharpConfig::reset(int clear) { switch(clear) { case RESET_ALL : radius = 0.1; amount = 0.0; threshold = 0; break; case RESET_RADIUS : radius = 0.1; break; case RESET_AMOUNT : amount = 0.0; break; case RESET_THRESHOLD : threshold = 0; break; case RESET_DEFAULT_SETTINGS : default: radius = 5; amount = 0.5; threshold = 0; break; } } int UnsharpConfig::equivalent(UnsharpConfig &that) { return EQUIV(radius, that.radius) && EQUIV(amount, that.amount) && threshold == that.threshold; } void UnsharpConfig::copy_from(UnsharpConfig &that) { radius = that.radius; amount = that.amount; threshold = that.threshold; } void UnsharpConfig::interpolate(UnsharpConfig &prev, UnsharpConfig &next, int64_t prev_frame, int64_t next_frame, int64_t current_frame) { double next_scale = (double)(current_frame - prev_frame) / (next_frame - prev_frame); double prev_scale = (double)(next_frame - current_frame) / (next_frame - prev_frame); this->radius = prev.radius * prev_scale + next.radius * next_scale; this->amount = prev.amount * prev_scale + next.amount * next_scale; this->threshold = (int)(prev.threshold * prev_scale + next.threshold * next_scale); } UnsharpMain::UnsharpMain(PluginServer *server) : PluginVClient(server) { engine = 0; } UnsharpMain::~UnsharpMain() { delete engine; } const char* UnsharpMain::plugin_title() { return N_("Unsharp"); } int UnsharpMain::is_realtime() { return 1; } NEW_WINDOW_MACRO(UnsharpMain, UnsharpWindow) LOAD_CONFIGURATION_MACRO(UnsharpMain, UnsharpConfig) void UnsharpMain::update_gui() { if(thread) { if(load_configuration()) { thread->window->lock_window("UnsharpMain::update_gui"); ((UnsharpWindow*)thread->window)->update_gui(RESET_DEFAULT_SETTINGS); thread->window->unlock_window(); } } } void UnsharpMain::save_data(KeyFrame *keyframe) { FileXML output; // cause data to be stored directly in text output.set_shared_output(keyframe->xbuf); output.tag.set_title("UNSHARP"); output.tag.set_property("RADIUS", config.radius); output.tag.set_property("AMOUNT", config.amount); output.tag.set_property("THRESHOLD", config.threshold); output.append_tag(); output.tag.set_title("/UNSHARP"); output.append_tag(); output.append_newline(); output.terminate_string(); } void UnsharpMain::read_data(KeyFrame *keyframe) { FileXML input; input.set_shared_input(keyframe->xbuf); int result = 0; while(!result) { result = input.read_tag(); if(!result) { if(input.tag.title_is("UNSHARP")) { config.radius = input.tag.get_property("RADIUS", config.radius); config.amount = input.tag.get_property("AMOUNT", config.amount); config.threshold = input.tag.get_property("THRESHOLD", config.threshold); } } } } int UnsharpMain::process_buffer(VFrame *frame, int64_t start_position, double frame_rate) { /*int need_reconfigure =*/ load_configuration(); if(!engine) engine = new UnsharpEngine(this, get_project_smp() + 1, get_project_smp() + 1); read_frame(frame, 0, get_source_position(), get_framerate(), 0); engine->do_unsharp(frame); return 0; } UnsharpPackage::UnsharpPackage() : LoadPackage() { } UnsharpUnit::UnsharpUnit(UnsharpEngine *server, UnsharpMain *plugin) : LoadClient(server) { this->plugin = plugin; this->server = server; temp = 0; } UnsharpUnit::~UnsharpUnit() { delete temp; } // Derived from the Gimp. // In the original file it says // // Copyright (C) 1999 Winston Chang // // // // Adapted for Cinelerra by Heroine Virtual Ltd. static int calculate_convolution_matrix(double radius, double **cmatrix) { radius = fabs(radius) + 1.0; double std_dev = radius; radius = std_dev * 2; int matrix_length = (int)(2 * ceil(radius - 0.5) + 1); matrix_length = MAX(1, matrix_length); // int matrix_midpoint = matrix_length / 2 + 1; (*cmatrix) = new double[matrix_length]; // Top right of matrix for(int i = matrix_length / 2 + 1; i < matrix_length; i++) { double base_x = i - floor(matrix_length / 2) - 0.5; double sum = 0; for(int j = 1; j <= 50; j++) { if(base_x + 0.02 * j <= radius) { sum += exp(-(base_x + 0.02 * j) * (base_x + 0.02 * j) / (2 * std_dev * std_dev)); } } (*cmatrix)[i] = sum / 50; } // Top left of matrix for(int i = 0; i < matrix_length / 2; i++) { (*cmatrix)[i] = (*cmatrix)[matrix_length - 1 - i]; } // Center value double sum = 0; for(int j = 0; j <= 50; j++) { sum += exp(-(0.5 + 0.02 * j) * (0.5 + 0.02 * j) / (2 * std_dev * std_dev)); } (*cmatrix)[matrix_length / 2] = sum / 51; // Normalize sum = 0; for(int i = 0; i < matrix_length; i++) sum += (*cmatrix)[i]; for(int i = 0; i < matrix_length; i++) (*cmatrix)[i] = (*cmatrix)[i] / sum; return matrix_length; } static double get_convolution(double *cmatrix, float input, int index) { return cmatrix[index] * input; } static void blur_pixels(double *cmatrix, int cmatrix_length, float *input, float *output, int pixels, int components) { if(cmatrix_length > pixels) { for(int pixel = 0; pixel < pixels; pixel++) { double scale = 0; for(int j = 0; j < pixels; j++) { if((j + cmatrix_length / 2 - pixel >= 0) && (j + cmatrix_length / 2 - pixel < cmatrix_length)) { scale += cmatrix[j + cmatrix_length / 2 - pixel]; } } for(int i = 0; i < components; i++) { double sum = 0; for(int j = 0; j < pixels; j++) { if((j >= pixel - cmatrix_length / 2) && (j <= pixel + cmatrix_length / 2)) { sum += input[j * components + i] * cmatrix[i]; } } output[pixel * components + i] = sum / scale; } } } else { int cmatrix_middle = cmatrix_length / 2; int pixel; for(pixel = 0; pixel < cmatrix_middle; pixel++) { double scale = 0; for(int j = cmatrix_middle - pixel; j < cmatrix_length;j++) { scale += cmatrix[j]; } for(int i = 0; i < components; i++) { double sum = 0; for(int j = cmatrix_middle - pixel; j < cmatrix_length; j++) { sum += input[(pixel + j - cmatrix_middle) * components + i] * cmatrix[j]; } output[pixel * components + i] = sum / scale; } } float *output_ptr = output + pixel * components; for( ; pixel < pixels - cmatrix_middle; pixel++) { float *input_ptr = input + (pixel - cmatrix_middle) * components; for(int i = 0; i < components; i++) { double sum = 0; float *input_ptr2 = input_ptr; for(int j = cmatrix_length; j > 0; j--) { sum += get_convolution(cmatrix, *input_ptr2, cmatrix_length - j); input_ptr2 += components; } input_ptr++; *output_ptr++ = sum; } } for( ; pixel < pixels; pixel++) { double scale = 0; for(int j = 0; j < pixels - pixel + cmatrix_middle; j++) { scale += cmatrix[j]; } for(int i = 0; i < components; i++) { double sum = 0; for(int j = 0; j < pixels - pixel + cmatrix_middle; j++) { sum += input[(pixel + j - cmatrix_middle) * components + i] * cmatrix[j]; } output[pixel * components + i] = sum / scale; } } } } #define GET_ROW(type, components) \ { \ type *in_row = (type*)src->get_rows()[row]; \ int pixels = src->get_w() * components; \ for(int i = 0; i < pixels; i++) \ { \ dst[i] = in_row[i]; \ } \ } static void get_row(float *dst, VFrame *src, int row) { switch(src->get_color_model()) { case BC_RGB888: case BC_YUV888: GET_ROW(unsigned char, 3); break; case BC_RGB_FLOAT: GET_ROW(float, 3); break; case BC_RGBA8888: case BC_YUVA8888: GET_ROW(unsigned char, 4); break; case BC_RGBA_FLOAT: GET_ROW(float, 4); break; case BC_YUV161616: GET_ROW(uint16_t, 3); break; case BC_YUVA16161616: GET_ROW(uint16_t, 4); break; } } static void get_column(float *dst, VFrame *src, int column) { int components = BC_CModels::components(src->get_color_model()); for(int i = 0; i < src->get_h(); i++) { float *input_pixel = (float*)src->get_rows()[i] + column * components; memcpy(dst, input_pixel, sizeof(float) * components); dst += components; } } static void put_column(float *src, VFrame *dst, int column) { int components = BC_CModels::components(dst->get_color_model()); for(int i = 0; i < dst->get_h(); i++) { float *output_pixel = (float*)dst->get_rows()[i] + column * components; memcpy(output_pixel, src, sizeof(float) * components); src += components; } } void UnsharpUnit::process_package(LoadPackage *package) { UnsharpPackage *pkg = (UnsharpPackage*)package; // int w = server->src->get_w(); // int h = server->src->get_h(); int color_model = server->src->get_color_model(); int components = BC_CModels::components(color_model); double *cmatrix = 0; int cmatrix_length = 0; int padded_y1 = pkg->y1; int padded_y2 = pkg->y2; cmatrix_length = calculate_convolution_matrix( plugin->config.radius, &cmatrix); if(padded_y2 < server->src->get_h()) { padded_y2 += cmatrix_length / 2; padded_y2 = MIN(server->src->get_h(), padded_y2); } if(padded_y1 > 0) { padded_y1 -= cmatrix_length / 2; padded_y1 = MAX(0, padded_y1); } int padded_rows = padded_y2 - padded_y1; if(!temp || temp->get_h() != padded_rows) { delete temp; temp = 0; } if(!temp) { temp = new VFrame; temp->set_use_shm(0); temp->reallocate(0, -1, 0, 0, 0, server->src->get_w(), padded_rows, components == 3 ? BC_RGB_FLOAT : BC_RGBA_FLOAT, -1); } float *temp_in = new float[MAX(temp->get_w(), padded_rows) * components]; float *temp_out = new float[MAX(temp->get_w(), padded_rows) * components]; // Blur rows for(int i = padded_y1; i < padded_y2; i++) { get_row(temp_in, server->src, i); blur_pixels(cmatrix, cmatrix_length, temp_in, temp_out, temp->get_w(), components); // printf("UnsharpUnit::process_package %d %p %p %p %d %d\n", // __LINE__, // temp, // temp->get_rows()[0], // temp_out, // i - padded_y1, // temp->get_bytes_per_line()); memcpy(temp->get_rows()[i - padded_y1], temp_out, temp->get_bytes_per_line()); } //Now we're 100% floating point. Blur the columns for(int i = 0; i < temp->get_w(); i++) { get_column(temp_in, temp, i); blur_pixels(cmatrix, cmatrix_length, temp_in, temp_out, padded_rows, components); put_column(temp_out, temp, i); } //printf("%f %f %d\n", plugin->config.radius,plugin->config.amount, plugin->config.threshold); #define UNSHARPEN(type, components, max) \ { \ float threshold = (float)plugin->config.threshold * max / 0xff; \ float amount = plugin->config.amount; \ \ for(int i = pkg->y1; i < pkg->y2; i++) \ { \ float *blurry_row = (float*)temp->get_rows()[i - padded_y1]; \ type *orig_row = (type*)server->src->get_rows()[i]; \ for(int j = 0; j < server->src->get_w(); j++) \ { \ for(int k = 0; k < components; k++) \ { \ float diff = *orig_row - *blurry_row; \ if(fabsf(2 * diff) < threshold) \ diff = 0; \ float value = *orig_row + amount * diff; \ if(sizeof(type) == 4) \ *orig_row = (type)value; \ else \ *orig_row = (type)CLIP(value, 0, max); \ blurry_row++; \ orig_row++; \ } \ } \ } \ } // Apply unsharpening switch(color_model) { case BC_RGB888: case BC_YUV888: UNSHARPEN(unsigned char, 3, 0xff); break; case BC_RGBA8888: case BC_YUVA8888: UNSHARPEN(unsigned char, 4, 0xff); break; case BC_RGB_FLOAT: UNSHARPEN(float, 3, 1.0); break; case BC_RGBA_FLOAT: UNSHARPEN(float, 4, 1.0); break; case BC_YUV161616: UNSHARPEN(uint16_t, 3, 0xffff); break; case BC_YUVA16161616: UNSHARPEN(uint16_t, 4, 0xffff); break; } delete [] temp_in; delete [] temp_out; delete [] cmatrix; } UnsharpEngine::UnsharpEngine(UnsharpMain *plugin, int total_clients, int total_packages) : LoadServer( //1, 1 total_clients, total_packages ) { this->plugin = plugin; } UnsharpEngine::~UnsharpEngine() { } void UnsharpEngine::init_packages() { for(int i = 0; i < get_total_packages(); i++) { UnsharpPackage *pkg = (UnsharpPackage*)get_package(i); pkg->y1 = src->get_h() * i / get_total_packages(); pkg->y2 = src->get_h() * (i + 1) / get_total_packages(); } } LoadClient* UnsharpEngine::new_client() { return new UnsharpUnit(this, plugin); } LoadPackage* UnsharpEngine::new_package() { return new UnsharpPackage; } void UnsharpEngine::do_unsharp(VFrame *src) { this->src = src; process_packages(); }