How I can display in OpenGL an image using the system color profile?

Viewed 462

I'm loading a texture using OpenGL like this

glTexImage2D(
  GL_TEXTURE_2D,
  0,
  GL_RGBA,
  texture.width,
  texture.height,
  0,
  GL_RGBA,
  GL_UNSIGNED_BYTE,
  texture.pixels.data());

The issue is that the color of the image looks different from the one I see when I open the file on the system image viewer.

On the screenshot you can see the yellow on the face displayed on the system image viewer has the color #FEDE57 but the one that is displayed in the OpenGL window is #FEE262

screenshot that shows OpenGL is not displaying color properly

Is there any flag or format I could use to match the same color calibration?

Displaying this same image as a Vulkan texture looks fine, so I can discard there is not an issue in how I load the image data.

3 Answers

@Tokenyet and @t.niese are pretty much correct.

You need to approximately power you final colour's rgb values by 1.0/2.2. Something on the line of this:

FragColor.rgb = pow(fragColor.rgb, vec3(1.0/gamma)); //gamma --> float = 2.2

Note: this should be the final/last statement in the fragment shader. Do all your lighting and colour calculations before this, or else the result will be weird because you will be mixing linear and non-linear lighting (calculations).

The reason you need to do gamma correction is because the human eye perceives colour differently to what the computer outputs.

enter image description here

If the light intensity (lux) increases by twice the amount, your eye indeed sees it twice as bright. However, the actual brightness, when increased by twice the amount, increases in a logarithmic (or exponential?, someone please correct me here) relationship. The constant of proportionality between the two lighting spaces is ^2.2 (or ^(1.0/2.2) if you want to go the inverse (which is what you are looking for.)).

For more info: Look at this great tutorial on gamma correction!

Note 2: This is an approximation. Each computer, program, API have their own auto gamma correction method. You system image viewer may have different gamma correction methods (or not even have any for that matter) compared to OpenGL

Note 3: Btw, if this does not work, there are manual methods to adjust the colour in the fragment shader, if you know.

#FEDE57 = RGB(254, 222, 87)

which converted into OpenGL colour coordinates is,

(254, 222, 87) / 255 = vec3(0.9961, 0.8706, 0.3412)

Both images and displays have a gamma value.

If GL_FRAMEBUFFER_SRGB is not enabled then:

the system assumes that the color written by the fragment shader is in whatever colorspace the image it is being written to is. Therefore, no colorspace correction is performed.

( khronos: Framebuffer - Colorspace )

So in that case you need to figure out what the gamma value of the image you read in is and what the one of the output medium is and do the corresponding conversion between those.

To get the one of the output medium is however not always easy.

Therefore it is preferred to enable GL_FRAMEBUFFER_SRGB

If GL_FRAMEBUFFER_SRGB is enabled however, then if the destination image is in the sRGB colorspace […], then it will assume the shader's output is in the linear RGB colorspace. It will therefore convert the output from linear RGB to sRGB.

( khronos: Framebuffer - Colorspace )

So in that case you only need to ensure that the colors you set in the fragment shader don't have gamma correction applied but are linear.

So what you normally do is to get the gamma information of the image, which is done with a certain function of the library you use to read the image.

If the gamma of the image you read is gamma you can calculate the value to invert it with inverseGamme = 1. / gamma, and then you can use pixelColor.channel = std::pow(pixelColor.channel, inverseGamme) for each of the color channels and each pixel to make the color space linear.

You will use this values in the linear color space as texture data.

You could also use something like GL_SRGB8 for the texture, but then you would need to convert the values of the pixels you read form the image to sRGB colorspace, which roughly is done by first linearizing it and then applying a gamma of 2.2

In the end it seems like the framebuffer in OpenGL doesn't gets color corrected, so you have to tell the OS to do it for you

#include <Cocoa/Cocoa.h>

void prepareNativeWindow(SDL_Window *sdlWindow)
{
  SDL_SysWMinfo wmi;
  SDL_VERSION(&wmi.version);
  SDL_GetWindowWMInfo(sdlWindow, &wmi);
  NSWindow *win = wmi.info.cocoa.window;
  [win setColorSpace:[NSColorSpace sRGBColorSpace]];
}

I found this solution here https://github.com/google/filament/blob/main/libs/filamentapp/src/NativeWindowHelperCocoa.mm

Related