/* * calculations.c: calculations needed by the input devices * * See the README file for copyright information and how to reach the author. * * $Id$ */ #include #include #include "math.h" #include "defs.h" #include "setup.h" #include "calculations.h" // set accuracy of color calculation #define h_MAX 255 #define s_MAX 255 #define v_MAX 255 // macros #define MIN(X, Y) ((X) < (Y) ? (X) : (Y)) #define MAX(X, Y) ((X) > (Y) ? (X) : (Y)) #define POS_DIV(a, b) ( (a)/(b) + ( ((a)%(b) >= (b)/2 ) ? 1 : 0) ) tColorPacket CalcColorsAnalyzeHSV(tHSVColor *HSV_Img) { int i; // counter static tWeightPacket Weight[IMAGE_SIZE]; /***************************************************************************/ /* Weight */ /***************************************************************************/ static int LastEdgeWeighting = -1; static int LastWidescreenMode = -1; // calculate only if setup has changed if ((AtmoSetup.EdgeWeighting != LastEdgeWeighting) || (AtmoSetup.WidescreenMode != LastWidescreenMode)) { i = 0; for (int row = 0; row < CAP_HEIGHT; row++) { float row_norm = (float)row / ((float)CAP_HEIGHT - 1.0); // [0;Height] -> [0;1] float weight_3 = pow(1.0 - row_norm, AtmoSetup.EdgeWeighting); // top float weight_4 = pow(row_norm, AtmoSetup.EdgeWeighting); // bottom for (int column = 0; column < CAP_WIDTH; column++) { // if widescreen mode, top and bottom of the picture are not if ((AtmoSetup.WidescreenMode == 1) && ((row <= CAP_HEIGHT/8) || (row >= (7*CAP_HEIGHT)/8))) { Weight[i].channel[0] = Weight[i].channel[1] = Weight[i].channel[2] = Weight[i].channel[3] = Weight[i].channel[4] = 0; } else { float column_norm = (float)column / ((float)CAP_WIDTH - 1.0); // [0;Width] -> [0;1] Weight[i].channel[0] = 255; Weight[i].channel[1] = (int)(255.0 * (float)pow((1.0 - column_norm), AtmoSetup.EdgeWeighting)); Weight[i].channel[2] = (int)(255.0 * (float)pow(column_norm, AtmoSetup.EdgeWeighting)); Weight[i].channel[3] = (int)(255.0 * (float)weight_3); Weight[i].channel[4] = (int)(255.0 * (float)weight_4); } i++; } } LastEdgeWeighting = AtmoSetup.EdgeWeighting; LastWidescreenMode = AtmoSetup.WidescreenMode; } /***************************************************************************/ /* Hue */ /***************************************************************************/ /*----------------------------*/ /* hue histogram builtup */ /*----------------------------*/ long int hue_hist[5][h_MAX+1]; // HSV histogram memset(&hue_hist, 0, sizeof(hue_hist)); // clean histogram i = 0; for (int row = 0; row < CAP_HEIGHT; row++) { for (int column = 0; column < CAP_WIDTH; column++) { // forget black bars: perform calculations only if pixel has some luminosity if (HSV_Img[i].v > 10*AtmoSetup.DarknessLimit) { // builtup histogram for the 5 channels for (int channel = 0; channel < 5; channel++) { // Add weight to channel hue_hist[channel][HSV_Img[i].h] += Weight[i].channel[channel] * HSV_Img[i].v; } } i++; } } /*----------------------------*/ /* hue histogram windowing */ /*----------------------------*/ long int w_hue_hist[5][h_MAX+1]; // windowed HSV histogram memset(&w_hue_hist, 0, sizeof(w_hue_hist)); // clean windowed histogram int hue_windowsize = AtmoSetup.HueWinSize; // steps in each direction; eg. 2 => -2 -1 0 1 2 windowing for (i = 0; i < h_MAX+1; i++) // walk through histogram [0;h_MAX] { // windowing from -hue_windowsize -> +hue_windowsize for (int mywin = -hue_windowsize; mywin < hue_windowsize+1; mywin++) { int myidx = i + mywin; // adressed histogram candlestick if (myidx < 0) { myidx = myidx + h_MAX + 1; } // handle beginning of windowing -> roll back if (myidx > h_MAX) { myidx = myidx - h_MAX - 1; } // handle end of windowing -> roll forward // Apply windowing to all 5 channels for (int channel = 0; channel < 5; channel++) { // apply lite triangular window design with gradient of 10% per discrete step w_hue_hist[channel][i] += hue_hist[channel][myidx] * ((hue_windowsize+1)-abs(mywin)); // apply window } } } /*--------------------------------------*/ /* analyze histogram for most used hue */ /*--------------------------------------*/ static int most_used_hue_last[5] = {0, 0, 0, 0, 0}; // index of last maximum int most_used_hue[5]; // resulting hue for each channel memset(&most_used_hue, 0, sizeof(most_used_hue)); for (int channel = 0; channel < 5; channel++) { int value = 0; for (i = 0; i < h_MAX+1; i++) // walk through histogram { if (w_hue_hist[channel][i] > value) // if new value bigger then old one { most_used_hue[channel] = i; // remember index value = w_hue_hist[channel][i]; // and value } } float percent = (float)w_hue_hist[channel][most_used_hue_last[channel]] / (float)value; if (percent > 0.93f) // less than 7% difference? { most_used_hue[channel] = most_used_hue_last[channel]; // use last index } most_used_hue_last[channel] = most_used_hue[channel]; // save current index of most used hue } /***************************************************************************/ /* saturation */ /***************************************************************************/ long int sat_hist[5][s_MAX+1]; // sat histogram int pixel_hue = 0; // hue of the pixel we are working at memset(&sat_hist, 0, sizeof(sat_hist)); // clean histogram /*--------------------------------------*/ /* saturation histogram builtup */ /*--------------------------------------*/ i = 0; for (int row = 0; row < CAP_HEIGHT; row++) { for (int column = 0; column < CAP_WIDTH; column++) { // forget black bars: perform calculations only if pixel has some luminosity if (HSV_Img[i].v > 10*AtmoSetup.DarknessLimit) { // find histogram position for pixel pixel_hue = HSV_Img[i].h; // TODO: evtl. hier brightness-berechnung for (int channel = 0; channel < 5; channel++) { // only use pixel for histogram if hue is near most_used_hue if ((pixel_hue > most_used_hue[channel] - hue_windowsize) && (pixel_hue < most_used_hue[channel] + hue_windowsize)) { // build histogram sat_hist[channel][HSV_Img[i].s] += Weight[i].channel[channel] * HSV_Img[i].v; } } } i++; } } /*--------------------------------------*/ /* saturation histogram windowing */ /*--------------------------------------*/ long int w_sat_hist[5][s_MAX+1]; // windowed HSV histogram memset(&w_sat_hist, 0, sizeof(w_sat_hist)); // clean windowed histogram int sat_windowsize = AtmoSetup.SatWinSize; // steps in each direction; eg. 2 => -2 -1 0 1 2 windowing for (i = 0; i < s_MAX + 1; i++) // walk through histogram [0;h_MAX] { // windowing from -hue_windowsize -> +hue_windowsize for (int mywin = -sat_windowsize; mywin < sat_windowsize+1; mywin++) { int myidx = i + mywin; // adressed histogram candlestick if (myidx < 0) { myidx = myidx + s_MAX + 1; } // handle beginning of windowing -> roll back if (myidx > h_MAX) { myidx = myidx - s_MAX - 1; } // handle end of windowing -> roll forward for (int channel = 0; channel < 5; channel++) { // apply lite triangular window design with gradient of 10% per discrete step w_sat_hist[channel][i] += sat_hist[channel][myidx] * ((sat_windowsize+1)-abs(mywin)); // apply window } } } /*--------------------------------------*/ /* analyze histogram for most used sat */ /*--------------------------------------*/ int most_used_sat[5]; // resulting sat (most_used_hue) for each channel memset(&most_used_sat, 0, sizeof(most_used_sat)); for (int channel = 0; channel < 5; channel++) { int value = 0; for (i = 0; i < s_MAX+1; i++) // walk trough histogram { if (w_sat_hist[channel][i] > value) // if new value bigger then old one { most_used_sat[channel] = i; // remember index value = w_sat_hist[channel][i]; // and value } } } /*----------------------------------------------------------*/ /* calculate average brightness within HSV image */ /* uniform Brightness for all channels is calculated */ /*----------------------------------------------------------*/ int l_counter = 0; long int value_avg = 0; // average brightness (value) // TODO: evtl. auslagern in sat-histo-built i = 0; for (int row = 0; row < CAP_HEIGHT; row++) { for (int column = 0; column < CAP_WIDTH; column++) { // find average value: only use bright pixels for luminance average if (HSV_Img[i].v > 10*AtmoSetup.DarknessLimit) { // build brightness average value_avg += HSV_Img[i].v; l_counter++; } i++; } } // calculate brightness average if (l_counter > 0) { value_avg = value_avg / l_counter; } else { value_avg = 10 * AtmoSetup.DarknessLimit; } /*----------------------------*/ /* adjust and copy results */ /*----------------------------*/ tHSVColor hsv_pixel; tColorPacket ColorChannels; // storage container for resulting RGB values for (int channel = 0; channel < 5; channel++) { // copy values hsv_pixel.h = most_used_hue[channel]; hsv_pixel.s = most_used_sat[channel]; // adjust brightness int new_value = (int) ((float)value_avg * ((float)AtmoSetup.BrightCorrect / 100.0)); if (new_value > 255) { new_value = 255; } // ensure brightness isn't set too high hsv_pixel.v = (unsigned char)new_value; ColorChannels.channel[channel] = HSV2RGB(hsv_pixel); // convert back to rgb } return ColorChannels; } tHSVColor RGB2HSV(tRGBColor color) { int min, max, delta; int r, g, b; int h = 0; tHSVColor hsv; r = color.r; g = color.g; b = color.b; min = MIN(MIN(r, g), b); max = MAX(MAX(r, g), b); delta = max - min; hsv.v = (unsigned char) POS_DIV( max*v_MAX, 255 ); if (delta == 0) // This is a gray, no chroma... { h = 0; // HSV results = 0 / 1 hsv.s = 0; } else // Chromatic data... { hsv.s = (unsigned char) POS_DIV( (delta*s_MAX) , max ); int dr = (max - r) + 3*delta; int dg = (max - g) + 3*delta; int db = (max - b) + 3*delta; int divisor = 6*delta; if (r == max) { h = POS_DIV(( (db - dg) * h_MAX ) , divisor); } else if (g == max) { h = POS_DIV( ((dr - db) * h_MAX) , divisor) + (h_MAX/3); } else if (b == max) { h = POS_DIV(( (dg - dr) * h_MAX) , divisor) + (h_MAX/3)*2; } if ( h < 0 ) { h += h_MAX; } if ( h > h_MAX ) { h -= h_MAX; } } hsv.h = (unsigned char)h; return hsv; } tRGBColor HSV2RGB(tHSVColor color) { tRGBColor rgb = {0, 0, 0}; float h = (float)color.h/(float)h_MAX; float s = (float)color.s/(float)s_MAX; float v = (float)color.v/(float)v_MAX; if (s == 0) { rgb.r = (int)((v*255.0)+0.5); rgb.g = rgb.r; rgb.b = rgb.r; } else { h = h * 6.0; if (h == 6.0) { h = 0.0; } int i = (int)h; float f = h - i; float p = v*(1.0f-s); float q = v*(1.0f-(s*f)); float t = v*(1.0f-(s*(1.0f-f))); if (i == 0) { rgb.r = (int)((v*255.0)+0.5); rgb.g = (int)((t*255.0)+0.5); rgb.b = (int)((p*255.0)+0.5); } else if (i == 1) { rgb.r = (int)((q*255.0)+0.5); rgb.g = (int)((v*255.0)+0.5); rgb.b = (int)((p*255.0)+0.5); } else if (i == 2) { rgb.r = (int)((p*255.0)+0.5); rgb.g = (int)((v*255.0)+0.5); rgb.b = (int)((t*255.0)+0.5); } else if (i == 3) { rgb.r = (int)((p*255.0)+0.5); rgb.g = (int)((q*255.0)+0.5); rgb.b = (int)((v*255.0)+0.5); } else if (i == 4) { rgb.r = (int)((t*255.0)+0.5); rgb.g = (int)((p*255.0)+0.5); rgb.b = (int)((v*255.0)+0.5); } else { rgb.r = (int)((v*255.0)+0.5); rgb.g = (int)((p*255.0)+0.5); rgb.b = (int)((q*255.0)+0.5); } } return rgb; }