When working with colors, Blender operates on values from a Linear color space. This allows to show colors and gradients in a way that is more familiar to the human eye, but this should always be taken into account when working directly with colors in Blender.
If we try to print the pixel color values for a red color image obtained, for example, from the compositing Viewer Node, we will get a set of values in a Linear color space.
1 2 3 4 5 |
bpy.data.images['Viewer Node'].pixels[:] # (0.5, 0.0, 0.0, 1.0, 0.5, 0.0, 0.0, 1.0, 0.5, 0.0, 0.0, 1.0, 0.5, 0.0, 0.0, 1.0, # 0.5, 0.0, 0.0, 1.0, 0.5, 0.0, 0.0, 1.0, 0.5, 0.0, 0.0, 1.0, 0.5, 0.0, 0.0, 1.0, # 0.5, 0.0, 0.0, 1.0, ... |
If we save such an array of pixels into an image, for example in PNG format, and then open it in a 2D graphics editor, the result will be noticeably different from what we expected.
This is because the sRGB color space is usually used for images, not Linear, as we got in the Blender output. In order to get the pixel values of an image in the sRGB color space suitable for saving, they need to be converted.
Let’s define a function that converts a value from the Linear color space to the sRGB color space:
1 2 3 4 5 |
def linear_to_srgb(color_value: float): if color_value <= 0.0031308: return int(12.92 * color_value * 255.99) else: return int((1.055 * color_value ** (1 / 2.4) - 0.055) * 255.99) |
In the function argument, we will pass a color value in the Linear color space in the range from 0.0 to 1.0. The function returns a value in the sRGB color space in the range from 0 to 255.
For example, for clear red color:
1 2 3 4 |
linear_color = 0.5 print(linear_color, '-->', linear_to_srgb(linear_color), '(', hex(linear_to_srgb(linear_color)), ')') # 0.5 --> 188 (0xbc) |
By converting all the color values of the pixels in this way, we can save it to a file without color loss.
We can also make the reverse conversion from the sRGB color space to the Linear.
1 2 3 4 5 6 |
def srgb_to_linear(value): value /= 255.99 if value <= 0.04045: return value / 12.92 else: return ((value + 0.055) / 1.055) ** 2.4 |
We will pass a color in the sRGB color space in the range from 0 to 255 to this function as a parameter, and at the output we will get a color in the Linear color space in the range from 0.0 to 1.0.
1 2 3 |
print('188 (0xbc)', '-->', round(srgb_to_linear(0xbc), 2)) # 188 (0xbc) --> 0.5 |