# Per pixel Image Computation with Gamma Correction in OpenGL and C [closed]

I've been working on a way to optimize pixel computation in OpenGL with C. If this information helps, my current environtment = Linux (terminal) + GPU (Nvidia 1080Ti). I'm working with old OpenGL (with GLUT) and not Modern OpenGL. Here's what I'm doing, this function will take image input, this is how I call the image

image = SOIL_load_image("Images/image_1.jpg", &width, &height, 0, SOIL_LOAD_RGBA);
if(image == NULL) exit(0);

image1 = NULL;
imgCompute();


Image computation processes are below :

• Convert RGB image → XYZ
• XYZ → xy chromaticity
• Do some computation to alter the pixel value
• Convert back xy → XYZ
• Convert back XYZ → RGB
• Convert RGB result → LMS
• Do computation in LMS
• Convert back LMS → RGB
float               fps   =  60.0;
/* image */
int                 xsize = 1600;
int                 ysize = 1000;

float              conv_x = 0.3;
float              conv_y = 0.25;

static void imgCompute()
{
unsigned char *p, *p1;
float R, G, B, r, g, b;
float X, Y, Z, X2, Y2, Z2, x ,y, z;
float l, m, s , L, M, S;
float *p2;

if(image1 == NULL) {
image1 = (unsigned char *)malloc(width*height*4);
if(image1 == NULL) exit(0);
image2 = (float *)malloc(sizeof(float)*width*height*5);
if(image2 == NULL) exit(0);

p = image;
p2 = image2;

for(int i = 0; i < height*width; i++) {

//gamma correction
R = pow(*(p+0)/255.0, 2.2);
G = pow(*(p+1)/255.0, 2.2);
B = pow(*(p+2)/255.0, 2.2);

//XYZ colorspace
X = (0.412453 * R) + (0.357580 * G) + (0.180423 * B);
Y = (0.212671 * R) + (0.715160 * G) + (0.072169 * B);
Z = (0.019334 * R) + (0.119193 * G) + (0.950227 * B);

x = X/(X+Y+Z);
y = Y/(X+Y+Z);
z = Z/(X+Y+Z);

p2[0] = x;
p2[1] = y;
p2[2] = Y;

//======================================================
struct Line equation = var(conv_x, conv_y, x, y);
struct Intersection i = point(equation);

struct Distance D = dist(i, x, y);
global = shift(conv_x, conv_y, D, x, y);

float x1 = global.x2;
float y1 = global.y2;
float x2 = global.x3;
float y2 = global.y3;
p2[3] = x1-x;
p2[4] = y1-y;
p2 += 5;
//=======================================================

p += 4;
}
}

p1 = image1;
p2 = image2;

for(int i = 0; i < height*width; i++) {

x = p2[0] + p2[3];
y = p2[1] + p2[4];
Y = p2[2];

X2 = (x * Y)/y;
Y2 =  Y;
Z2 = (1-x-y)*Y/y;

R = (  3.240479 * X2) + ( -1.53715  * Y2) + ( -0.498535 * Z2);
G = ( -0.969256 * X2) + (  1.875991 * Y2) + (  0.041556 * Z2);
B = (  0.055648 * X2) + ( -0.204043 * Y2) + (  1.057311 * Z2);

//============================================================
//lms colorspace

L = (17.8824 * R) + (43.5161 * G) + ( 4.1194 * B) ;
M = ( 3.4557 * R) + (27.1554 * G) + ( 3.8671 * B) ;
S = ( 0.0300 * R) + ( 0.1843 * G) + ( 1.4671 * B) ;

r = (  0.0209 * L) + ( -0.1005 * M) + (  0.0067 * S);
g = ( -0.0002 * L) + (  0.0003 * M) + ( -0.1006 * S);
b = ( -0.0004 * L) + ( -0.0021 * M) + (  0.3035 * S);

//gamma correction
*(p1+0) = pow(r, (1.0 / 2.2))*255.0;
*(p1+1) = pow(g, (1.0 / 2.2))*255.0;
*(p1+2) = pow(b, (1.0 / 2.2))*255.0;
*(p1+3) = 0;

p1 +=4;
p2 +=5;
}
}


My desired fps is 60fps and my original image size is 1600 x 1000. What I've been checking :

1. Original image + gamma correction applied = 3 fps.
2. Original image (no gamma correction) = 20 fps.

From tesult no.2 (20 fps), I tried to resize my image into 1/3 original size to see if I could achieve 60fps,

1. Resized image + gamma correction = 30 fps.
2. Resized image (no gamma correction) = 60 fps.

I've also checked each colorspace transformation, they are fine and don't affect the fps. As of now, the problems are image size and per pixel gamma computation. I'd appreciate some suggestions to improve performance of this function.

• Have you profiled your application and checked where the most time is spent ? Are you calling imgCompute every frame ? if yes your performance will be increased if you can avoid calling malloc every time but passing preallocated memory into this function. Otherwise I'm not sure if you are going to get much better performance without using any kind of paralelization or vectorization, be that on the CPU, or letting the GPU do the work and implementing your algorithms as shaders using OpenGL or via OpenCL. – Harald Scheirich Apr 11 '19 at 17:19
• Can you include a suitable main() to show how this would be used? It's hard to see how a function of no arguments and no result is intended to operate. We're also missing includes of <math.h>, <stdlib.h> and whatever defines struct Line, struct Intersection, struct Distance and their related functions. – Toby Speight Apr 12 '19 at 7:22
• Hi, @TobySpeight. I've checked my functions (including those inside header file) one by one and the function I put on my question is the one function that takes much time. Just this one function. Also, of course I call of my libraries. I'm trying to make post not as long. I put the link to the gist code on my post. – raisa_ Apr 14 '19 at 17:26
• What you may and may not do after receiving answers – Jamal Apr 15 '19 at 3:13

### Create lookup table

I ran your program on a buffer filled with random bytes and I found that most of the time was being spent doing the pow() operations. You can speed this part up by creating a lookup table, like this:

float powTable[256];

// Call this from main()
static void computeTable(void)
{
int i;
for (i = 0; i < 256; i++)
powTable[i] = pow(i/255.0, 2.2);
}

static void imgCompute()
{
// ...

//gamma correction
R = powTable[p[0]];
G = powTable[p[1]];
B = powTable[p[2]];

// ...
}


Adding this one lookup table cut the time of the function by 50%.

### Inverse gamma correction

I tried to do the same thing with the second set of pow() calls. However, that part was a bit tricky since instead of starting with an int in the range 0..255 and converting to a float in the range 0..1, you are starting with a float and converting to an int (i.e. the reverse function).

I actually implemented something that I thought would work (it was a lookup table that rounded the input float to the nearest 0.0001 and had 10000 entries). However, when I ran the program I discovered that a lot of the float values were either < 0.0, > 1.0, or even Nan. I traced that Nan back to these lines:

        X = (0.412453 * R) + (0.357580 * G) + (0.180423 * B);
Y = (0.212671 * R) + (0.715160 * G) + (0.072169 * B);
Z = (0.019334 * R) + (0.119193 * G) + (0.950227 * B);

x = X/(X+Y+Z);
y = Y/(X+Y+Z);
z = Z/(X+Y+Z);


Here, if R = G = B = 0, then X = Y = Z = 0. Then when you divide by (X+Y+Z), you get a division by zero. I'm not sure if that is a problem or not. Also, I'm not sure if the negative or greater than 1 values are problematic because when you apply the inverse gamma correction, you will get a pixel value outside the range 0..255. In any case, I decided to clamp the input values to the range 0..1 before doing the lookup. With the 2nd lookup table, it shaved another 50% of the time off. So with both lookup tables in place, the final program was about 4x faster than the original.

### No need for intermediate buffer

There is no need for the image2 intermediate buffer. If you combine your two processing loops, you will only ever need a 5 floating point intermediate buffer instead of allocating and filling a 1600 x 1000 x 5 x 4 = 32 MB buffer. Having such a big intermediate buffer could cause your program to run slower because it could cause your cpu cache to fill up. In my testing, it didn't make any difference in speed, but I would still recommend getting rid of that buffer and combining the two loops.

• Hi, thanks a lot ! It took me 2 days to implement the lookup table, I was having another issue with my code. Now my question is also put on hold (?) Now the gamma correction with lookup table works, but as you said the inverse gamma is problematic. I created another variable to store the inverse gamma, I've updated my code, would you please take a look at it ? As for image2, before I added more function, I started with just one image and it was so much slower, so I went with image2 and it's better now. – raisa_ Apr 14 '19 at 17:10