/* * 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 #include #include #include #include "bcdisplayinfo.h" #include "bcsignals.h" #include "clip.h" #include "bchash.h" #include "filexml.h" #include "bistogram.h" #include "bistogramconfig.h" #include "bistogramwindow.h" #include "keyframe.h" #include "language.h" #include "loadbalance.h" #include "cicolors.h" #include "vframe.h" class HistogramMain; class HistogramEngine; class HistogramWindow; REGISTER_PLUGIN(HistogramMain) HistogramMain::HistogramMain(PluginServer *server) : PluginVClient(server) { engine = 0; for(int i = 0; i < HISTOGRAM_MODES; i++) { lookup[i] = 0; smoothed[i] = 0; linear[i] = 0; accum[i] = 0; } current_point = -1; mode = HISTOGRAM_VALUE; dragging_point = 0; input = 0; output = 0; } HistogramMain::~HistogramMain() { for(int i = 0; i < HISTOGRAM_MODES;i++) { delete [] lookup[i]; delete [] smoothed[i]; delete [] linear[i]; delete [] accum[i]; } delete engine; } const char* HistogramMain::plugin_title() { return _("Histogram Bezier"); } int HistogramMain::is_realtime() { return 1; } NEW_WINDOW_MACRO(HistogramMain, HistogramWindow) LOAD_CONFIGURATION_MACRO(HistogramMain, HistogramConfig) void HistogramMain::render_gui(void *data) { if(thread) { calculate_histogram((VFrame*)data); if(config.automatic) { calculate_automatic((VFrame*)data); } HistogramWindow *window = (HistogramWindow *)thread->window; window->lock_window("HistogramMain::render_gui"); window->update_canvas(); if(config.automatic) { window->update_input(); } window->unlock_window(); } } void HistogramMain::update_gui() { if(thread) { thread->window->lock_window("HistogramMain::update_gui"); int reconfigure = load_configuration(); if(reconfigure) { HistogramWindow *window = (HistogramWindow *)thread->window; window->update(0); if(!config.automatic) { window->update_input(); } } thread->window->unlock_window(); } } void HistogramMain::save_data(KeyFrame *keyframe) { FileXML output; // cause data to be stored directly in text output.set_shared_output(keyframe->get_data(), MESSAGESIZE); output.tag.set_title("HISTOGRAM"); char string[BCTEXTLEN]; for(int i = 0; i < HISTOGRAM_MODES; i++) { sprintf(string, "OUTPUT_MIN_%d", i); output.tag.set_property(string, config.output_min[i]); sprintf(string, "OUTPUT_MAX_%d", i); output.tag.set_property(string, config.output_max[i]); //printf("HistogramMain::save_data %d %f %d\n", config.input_min[i], config.input_mid[i], config.input_max[i]); } output.tag.set_property("AUTOMATIC", config.automatic); output.tag.set_property("THRESHOLD", config.threshold); output.tag.set_property("SPLIT", config.split); output.tag.set_property("INTERPOLATION", config.smoothMode); output.append_tag(); output.tag.set_title("/HISTOGRAM"); output.append_tag(); output.append_newline(); for(int j = 0; j < HISTOGRAM_MODES; j++) { output.tag.set_title("POINTS"); output.append_tag(); output.append_newline(); HistogramPoint *current = config.points[j].first; while(current) { output.tag.set_title("POINT"); output.tag.set_property("X", current->x); output.tag.set_property("Y", current->y); output.tag.set_property("GRADIENT", current->gradient); output.tag.set_property("XOFFSET_LEFT", current->xoffset_left); output.tag.set_property("XOFFSET_RIGHT", current->xoffset_right); output.append_tag(); output.tag.set_title("/POINT"); output.append_tag(); output.append_newline(); current = NEXT; } output.tag.set_title("/POINTS"); output.append_tag(); output.append_newline(); } output.terminate_string(); } void HistogramMain::read_data(KeyFrame *keyframe) { FileXML input; input.set_shared_input(keyframe->get_data(), strlen(keyframe->get_data())); int result = 0; int current_input_mode = 0; while(!result) { result = input.read_tag(); if(!result) { if(input.tag.title_is("HISTOGRAM")) { char string[BCTEXTLEN]; for(int i = 0; i < HISTOGRAM_MODES; i++) { sprintf(string, "OUTPUT_MIN_%d", i); config.output_min[i] = input.tag.get_property(string, config.output_min[i]); sprintf(string, "OUTPUT_MAX_%d", i); config.output_max[i] = input.tag.get_property(string, config.output_max[i]); //printf("HistogramMain::read_data %d %f %d\n", config.input_min[i], config.input_mid[i], config.input_max[i]); } config.automatic = input.tag.get_property("AUTOMATIC", config.automatic); config.threshold = input.tag.get_property("THRESHOLD", config.threshold); config.split = input.tag.get_property("SPLIT", config.split); config.smoothMode = input.tag.get_property("INTERPOLATION", config.smoothMode); } else if(input.tag.title_is("POINTS")) { if(current_input_mode < HISTOGRAM_MODES) { HistogramPoints *points = &config.points[current_input_mode]; while(points->last) delete points->last; while(!result) { result = input.read_tag(); if(!result) { if(input.tag.title_is("/POINTS")) { break; } else if(input.tag.title_is("POINT")) { points->insert( input.tag.get_property("X", 0.0), input.tag.get_property("Y", 0.0)); points->last->gradient = input.tag.get_property("GRADIENT", 1.0); points->last->xoffset_left = input.tag.get_property("XOFFSET_LEFT", -0.02); points->last->xoffset_right = input.tag.get_property("XOFFSET_RIGHT", 0.02); } } } } current_input_mode++; } } } config.boundaries(); } float HistogramMain::calculate_linear(float input, int subscript, int use_value) { int done = 0; float output; if(input < 0) { output = 0; done = 1; } if(input > 1) { output = 1; done = 1; } if(!done) { float x1 = 0, y1 = 0; float grad1 = 1.0; float x1right = 0; float x2 = 1, y2 = 1; float grad2 = 1.0; float x2left = 0; // Get 2 points surrounding current position HistogramPoints *points = &config.points[subscript]; HistogramPoint *current = points->first; int done = 0; while(current && !done) { if(current->x > input) { x2 = current->x; y2 = current->y; grad2 = current->gradient; x2left = current->xoffset_left; done = 1; } else current = NEXT; } current = points->last; done = 0; while(current && !done) { if(current->x <= input) { x1 = current->x; y1 = current->y; grad1 = current->gradient; done = 1; x1right = current->xoffset_right; } else current = PREVIOUS; } if(!EQUIV(x2 - x1, 0)) { if (config.smoothMode == HISTOGRAM_LINEAR) output = (input - x1) * (y2 - y1) / (x2 - x1) + y1; else if (config.smoothMode == HISTOGRAM_POLYNOMINAL) { /* Construct third grade polynom between every two points */ float dx = x2 - x1; float dy = y2 - y1; float delx = input - x1; output = (grad2 * dx + grad1 * dx - 2*dy) / (dx * dx * dx) * delx * delx * delx + (3*dy - 2* grad1*dx - grad2*dx) / (dx * dx) * delx * delx + grad1*delx + y1; } else if (config.smoothMode == HISTOGRAM_BEZIER) { /* Using standart DeCasteljau algorithm */ float y1right = y1 + grad1 * x1right; float y2left = y2 + grad2 * x2left; float t = (input - x1) / (x2 - x1); float pointAy = y1 + (y1right - y1) * t; float pointBy = y1right + (y2left - y1right) * t; float pointCy = y2left + (y2 - y2left) * t; float pointABy = pointAy + (pointBy - pointAy) * t; float pointBCy = pointBy + (pointCy - pointBy) * t; output = pointABy + (pointBCy - pointABy) * t; } } else // Linear output = input * y2; } // Apply value curve if(use_value) { output = calculate_linear(output, HISTOGRAM_VALUE, 0); } float output_min = config.output_min[subscript]; float output_max = config.output_max[subscript]; // Compress output for value followed by channel output = output_min + output * (output_max - output_min); return output; } float HistogramMain::calculate_smooth(float input, int subscript) { float x_f = (input - HIST_MIN_INPUT) * HISTOGRAM_SLOTS / FLOAT_RANGE; int x_i1 = (int)x_f; int x_i2 = x_i1 + 1; CLAMP(x_i1, 0, HISTOGRAM_SLOTS-1); CLAMP(x_i2, 0, HISTOGRAM_SLOTS-1); CLAMP(x_f, 0, HISTOGRAM_SLOTS-1); float smooth1 = smoothed[subscript][x_i1]; float smooth2 = smoothed[subscript][x_i2]; float result = smooth1 + (smooth2 - smooth1) * (x_f - x_i1); CLAMP(result, 0, 1.0); return result; } void HistogramMain::calculate_histogram(VFrame *data) { if(!engine) engine = new HistogramEngine(this, get_project_smp() + 1, get_project_smp() + 1); if(!accum[0]) { for(int i = 0; i < HISTOGRAM_MODES; i++) accum[i] = new int[HISTOGRAM_SLOTS]; } engine->process_packages(HistogramEngine::HISTOGRAM, data); for(int i = 0; i < engine->get_total_clients(); i++) { HistogramUnit *unit = (HistogramUnit*)engine->get_client(i); if(i == 0) { for(int j = 0; j < HISTOGRAM_MODES; j++) memcpy(accum[j], unit->accum[j], sizeof(int) * HISTOGRAM_SLOTS); } else { for(int j = 0; j < HISTOGRAM_MODES; j++) { int *out = accum[j]; int *in = unit->accum[j]; for(int k = 0; k < HISTOGRAM_SLOTS; k++) out[k] += in[k]; } } } // Remove top and bottom from calculations. Doesn't work in high // precision colormodels. for(int i = 0; i < HISTOGRAM_MODES; i++) { accum[i][0] = 0; accum[i][HISTOGRAM_SLOTS - 1] = 0; } } void HistogramMain::calculate_automatic(VFrame *data) { calculate_histogram(data); config.reset_points(); // Do each channel for(int i = 0; i < 3; i++) { int *accum = this->accum[i]; int pixels = data->get_w() * data->get_h(); float white_fraction = 1.0 - (1.0 - config.threshold) / 2; int threshold = (int)(white_fraction * pixels); int total = 0; float max_level = 1.0; float min_level = 0.0; // Get histogram slot above threshold of pixels for(int j = 0; j < HISTOGRAM_SLOTS; j++) { total += accum[j]; if(total >= threshold) { max_level = (float)j / HISTOGRAM_SLOTS * FLOAT_RANGE + HIST_MIN_INPUT; break; } } // Get slot below 99% of pixels total = 0; for(int j = HISTOGRAM_SLOTS - 1; j >= 0; j--) { total += accum[j]; if(total >= threshold) { min_level = (float)j / HISTOGRAM_SLOTS * FLOAT_RANGE + HIST_MIN_INPUT; break; } } config.points[i].insert(max_level, 1.0); config.points[i].insert(min_level, 0.0); } } int HistogramMain::process_realtime(VFrame *input_ptr, VFrame *output_ptr) { SET_TRACE int need_reconfigure = load_configuration(); SET_TRACE if(!engine) engine = new HistogramEngine(this, get_project_smp() + 1, get_project_smp() + 1); this->input = input_ptr; this->output = output_ptr; send_render_gui(input_ptr); if(input_ptr->get_rows()[0] != output_ptr->get_rows()[0]) { output_ptr->copy_from(input_ptr); } SET_TRACE // Generate tables here. The same table is used by many packages to render // each horizontal stripe. Need to cover the entire output range in each // table to avoid green borders if( !lookup[0] || !smoothed[0] || !linear[0] || config.automatic) need_reconfigure = 1; if( need_reconfigure ) { SET_TRACE // Calculate new curves if(config.automatic) { calculate_automatic(input); } SET_TRACE // Generate transfer tables for integer colormodels. for(int i = 0; i < 3; i++) tabulate_curve(i, 1); SET_TRACE } // Apply histogram engine->process_packages(HistogramEngine::APPLY, input); SET_TRACE return 0; } void HistogramMain::tabulate_curve(int subscript, int use_value) { int i; if(!lookup[subscript]) lookup[subscript] = new int[HISTOGRAM_SLOTS]; if(!smoothed[subscript]) smoothed[subscript] = new float[HISTOGRAM_SLOTS]; if(!linear[subscript]) linear[subscript] = new float[HISTOGRAM_SLOTS]; float *current_smooth = smoothed[subscript]; float *current_linear = linear[subscript]; // Make linear curve for(i = 0; i < HISTOGRAM_SLOTS; i++) { float input = (float)i / HISTOGRAM_SLOTS * FLOAT_RANGE + HIST_MIN_INPUT; current_linear[i] = calculate_linear(input, subscript, use_value); } // Make smooth curve //float prev = 0.0; for(i = 0; i < HISTOGRAM_SLOTS; i++) { // current_smooth[i] = current_linear[i] * 0.001 + // prev * 0.999; current_smooth[i] = current_linear[i]; // prev = current_smooth[i]; } // Generate lookup tables for integer colormodels if(input) { switch(input->get_color_model()) { case BC_RGB888: case BC_RGBA8888: for(i = 0; i < 0x100; i++) lookup[subscript][i] = (int)(calculate_smooth((float)i / 0xff, subscript) * 0xff); break; // All other integer colormodels are converted to 16 bit RGB default: for(i = 0; i < 0x10000; i++) lookup[subscript][i] = (int)(calculate_smooth((float)i / 0xffff, subscript) * 0xffff); break; } } } HistogramPackage::HistogramPackage() : LoadPackage() { } HistogramUnit::HistogramUnit(HistogramEngine *server, HistogramMain *plugin) : LoadClient(server) { this->plugin = plugin; this->server = server; for(int i = 0; i < HISTOGRAM_MODES; i++) accum[i] = new int[HISTOGRAM_SLOTS]; } HistogramUnit::~HistogramUnit() { for(int i = 0; i < HISTOGRAM_MODES; i++) delete [] accum[i]; } void HistogramUnit::process_package(LoadPackage *package) { HistogramPackage *pkg = (HistogramPackage*)package; if(server->operation == HistogramEngine::HISTOGRAM) { #define HISTOGRAM_HEAD(type) \ { \ for(int i = pkg->start; i < pkg->end; i++) \ { \ type *row = (type*)data->get_rows()[i]; \ for(int j = 0; j < w; j++) \ { #define HISTOGRAM_TAIL(components) \ /* v = (r * 76 + g * 150 + b * 29) >> 8; */ \ v = MAX(r, g); \ v = MAX(v, b); \ r += -HISTOGRAM_MIN * 0xffff / 100; \ g += -HISTOGRAM_MIN * 0xffff / 100; \ b += -HISTOGRAM_MIN * 0xffff / 100; \ v += -HISTOGRAM_MIN * 0xffff / 100; \ CLAMP(r, 0, HISTOGRAM_SLOTS-1); \ CLAMP(g, 0, HISTOGRAM_SLOTS-1); \ CLAMP(b, 0, HISTOGRAM_SLOTS-1); \ CLAMP(v, 0, HISTOGRAM_SLOTS-1); \ accum_r[r]++; \ accum_g[g]++; \ accum_b[b]++; \ accum_v[v]++; \ row += components; \ } \ } \ } VFrame *data = server->data; int w = data->get_w(); // int h = data->get_h(); int *accum_r = accum[HISTOGRAM_RED]; int *accum_g = accum[HISTOGRAM_GREEN]; int *accum_b = accum[HISTOGRAM_BLUE]; int *accum_v = accum[HISTOGRAM_VALUE]; int r, g, b, y, u, v; switch(data->get_color_model()) { case BC_RGB888: HISTOGRAM_HEAD(unsigned char) r = (row[0] << 8) | row[0]; g = (row[1] << 8) | row[1]; b = (row[2] << 8) | row[2]; HISTOGRAM_TAIL(3) break; case BC_RGB_FLOAT: HISTOGRAM_HEAD(float) r = (int)(row[0] * 0xffff); g = (int)(row[1] * 0xffff); b = (int)(row[2] * 0xffff); HISTOGRAM_TAIL(3) break; case BC_YUV888: HISTOGRAM_HEAD(unsigned char) y = (row[0] << 8) | row[0]; u = (row[1] << 8) | row[1]; v = (row[2] << 8) | row[2]; plugin->yuv.yuv_to_rgb_16(r, g, b, y, u, v); HISTOGRAM_TAIL(3) break; case BC_RGBA8888: HISTOGRAM_HEAD(unsigned char) r = (row[0] << 8) | row[0]; g = (row[1] << 8) | row[1]; b = (row[2] << 8) | row[2]; HISTOGRAM_TAIL(4) break; case BC_RGBA_FLOAT: HISTOGRAM_HEAD(float) r = (int)(row[0] * 0xffff); g = (int)(row[1] * 0xffff); b = (int)(row[2] * 0xffff); HISTOGRAM_TAIL(4) break; case BC_YUVA8888: HISTOGRAM_HEAD(unsigned char) y = (row[0] << 8) | row[0]; u = (row[1] << 8) | row[1]; v = (row[2] << 8) | row[2]; plugin->yuv.yuv_to_rgb_16(r, g, b, y, u, v); HISTOGRAM_TAIL(4) break; case BC_RGB161616: HISTOGRAM_HEAD(uint16_t) r = row[0]; g = row[1]; b = row[2]; HISTOGRAM_TAIL(3) break; case BC_YUV161616: HISTOGRAM_HEAD(uint16_t) y = row[0]; u = row[1]; v = row[2]; plugin->yuv.yuv_to_rgb_16(r, g, b, y, u, v); HISTOGRAM_TAIL(3) break; case BC_RGBA16161616: HISTOGRAM_HEAD(uint16_t) r = row[0]; g = row[1]; b = row[2]; HISTOGRAM_TAIL(3) break; case BC_YUVA16161616: HISTOGRAM_HEAD(uint16_t) y = row[0]; u = row[1]; v = row[2]; plugin->yuv.yuv_to_rgb_16(r, g, b, y, u, v); HISTOGRAM_TAIL(4) break; } } else if(server->operation == HistogramEngine::APPLY) { #define PROCESS(type, components) \ { \ for(int i = pkg->start; i < pkg->end; i++) \ { \ type *row = (type*)input->get_rows()[i]; \ for(int j = 0; j < w; j++) \ { \ if ( plugin->config.split && ((j + i * w / h) < w) ) \ continue; \ row[0] = lookup_r[row[0]]; \ row[1] = lookup_g[row[1]]; \ row[2] = lookup_b[row[2]]; \ row += components; \ } \ } \ } #define PROCESS_YUV(type, components, max) \ { \ for(int i = pkg->start; i < pkg->end; i++) \ { \ type *row = (type*)input->get_rows()[i]; \ for(int j = 0; j < w; j++) \ { \ if ( plugin->config.split && ((j + i * w / h) < w) ) \ continue; \ /* Convert to 16 bit RGB */ \ if(max == 0xff) \ { \ y = (row[0] << 8) | row[0]; \ u = (row[1] << 8) | row[1]; \ v = (row[2] << 8) | row[2]; \ } \ else \ { \ y = row[0]; \ u = row[1]; \ v = row[2]; \ } \ \ plugin->yuv.yuv_to_rgb_16(r, g, b, y, u, v); \ \ /* Look up in RGB domain */ \ r = lookup_r[r]; \ g = lookup_g[g]; \ b = lookup_b[b]; \ \ /* Convert to 16 bit YUV */ \ plugin->yuv.rgb_to_yuv_16(r, g, b, y, u, v); \ \ if(max == 0xff) \ { \ row[0] = y >> 8; \ row[1] = u >> 8; \ row[2] = v >> 8; \ } \ else \ { \ row[0] = y; \ row[1] = u; \ row[2] = v; \ } \ row += components; \ } \ } \ } #define PROCESS_FLOAT(components) \ { \ for(int i = pkg->start; i < pkg->end; i++) \ { \ float *row = (float*)input->get_rows()[i]; \ for(int j = 0; j < w; j++) \ { \ if ( plugin->config.split && ((j + i * w / h) < w) ) \ continue; \ float r = row[0]; \ float g = row[1]; \ float b = row[2]; \ \ r = plugin->calculate_smooth(r, HISTOGRAM_RED); \ g = plugin->calculate_smooth(g, HISTOGRAM_GREEN); \ b = plugin->calculate_smooth(b, HISTOGRAM_BLUE); \ \ row[0] = r; \ row[1] = g; \ row[2] = b; \ \ row += components; \ } \ } \ } VFrame *input = plugin->input; // VFrame *output = plugin->output; int w = input->get_w(), h = input->get_h(); int *lookup_r = plugin->lookup[0]; int *lookup_g = plugin->lookup[1]; int *lookup_b = plugin->lookup[2]; int r, g, b, y, u, v; switch(input->get_color_model()) { case BC_RGB888: PROCESS(unsigned char, 3) break; case BC_RGB_FLOAT: PROCESS_FLOAT(3); break; case BC_RGBA8888: PROCESS(unsigned char, 4) break; case BC_RGBA_FLOAT: PROCESS_FLOAT(4); break; case BC_RGB161616: PROCESS(uint16_t, 3) break; case BC_RGBA16161616: PROCESS(uint16_t, 4) break; case BC_YUV888: PROCESS_YUV(unsigned char, 3, 0xff) break; case BC_YUVA8888: PROCESS_YUV(unsigned char, 4, 0xff) break; case BC_YUV161616: PROCESS_YUV(uint16_t, 3, 0xffff) break; case BC_YUVA16161616: PROCESS_YUV(uint16_t, 4, 0xffff) break; } } } HistogramEngine::HistogramEngine(HistogramMain *plugin, int total_clients, int total_packages) : LoadServer(total_clients, total_packages) { this->plugin = plugin; } void HistogramEngine::init_packages() { switch(operation) { case HISTOGRAM: total_size = data->get_h(); break; case APPLY: total_size = data->get_h(); break; } for(int i = 0; i < get_total_packages(); i++) { HistogramPackage *package = (HistogramPackage*)get_package(i); package->start = total_size * i / get_total_packages(); package->end = total_size * (i + 1) / get_total_packages(); } // Initialize clients here in case some don't get run. for(int i = 0; i < get_total_clients(); i++) { HistogramUnit *unit = (HistogramUnit*)get_client(i); for(int i = 0; i < HISTOGRAM_MODES; i++) bzero(unit->accum[i], sizeof(int) * HISTOGRAM_SLOTS); } } LoadClient* HistogramEngine::new_client() { return new HistogramUnit(this, plugin); } LoadPackage* HistogramEngine::new_package() { return new HistogramPackage; } void HistogramEngine::process_packages(int operation, VFrame *data) { this->data = data; this->operation = operation; LoadServer::process_packages(); }