# How to convert image pixel values from 8bit to 32bit properly?

I'm testing code from this question:



import bpy

width = 800
height = 400

image = bpy.data.images.new("testimagepacked", width=width, height=height,float_buffer=True)

pixels = [None] * width * height

for x in range(0,width):
for y in range(0,height):
pixels[(y*width)+x] = [float(x)/width,float(y)/height,-y+x,1.0]

pixels = [chan for px in pixels for chan in px]
image.pixels = pixels
image.update()




I noticed the code above generates different colors depending on the float_buffer being True or False:

From what I understand - pixel (RGBA) values for 8bit are in range from [0..1], so:

1) How is 32bpc different in that regard?

2) Is there a way to create 8 and 32bpc images, that would result in identical (visually) colors from the same code (with different RGB values maybe)?

3) Also - if I had an 8bit image - how could I transfer pixel values to a 32bit image and keep it visually consistent?

EDIT: Investigating the issue, references:

How blender load images

python api reference

linear to srgb formula

''

import bpy

width = 100
height = 50

image = bpy.data.images.new("float buffer true", width=width, height=height,float_buffer=True)
pixels = [None] * width * height

for x in range(0,width):
for y in range(0,height):
pixels[(y*width)+x] = [float(x)/width,float(y)/height,-y+x,1.0]

pixels = [chan for px in pixels for chan in px]
image.pixels = pixels
image.update()

image = bpy.data.images.new("float buffer false", width=width, height=height,float_buffer=False)
pixels = [None] * width * height

for x in range(0,width):
for y in range(0,height):
pixels[(y*width)+x] = [float(x)/width,float(y)/height,-y+x,1.0]

pixels = [chan for px in pixels for chan in px]
image.pixels = pixels
image.update()

def convert_to_srgb(val):
if (val <= 0.0031308):
return (val * 12.92)
else:
return (1.055*(val**(1.0/2.4))-0.055)

image = bpy.data.images.new("float buffer false converted", width=width, height=height,float_buffer=False)
pixels = [None] * width * height

for x in range(0,width):
for y in range(0,height):
pixels[(y*width)+x] = [ convert_to_srgb(float(x)/width),
convert_to_srgb(float(y)/height),
convert_to_srgb(-y+x),
1.0
]

pixels = [chan for px in pixels for chan in px]
image.pixels = pixels
image.update()


''

This code result:

And color comparison on one point:

So my question now (since the original question found it's answer): is this behavior expected? Am I missing a point here? I mean: if I write a given color value directly to pixel... the result should be the same inspite the fact that image has floating_point buffer True or False? or not? Especially that Blender states it does all color data manipulation in linear values (it still might be true - only 8bit images values seem to be converted on the fly somewhere, where it isn't exposed to the python api?).

Why is this confusing for me? Because I'd understand why 8bpc might store raw pixel data differently from EXR's, like troy_s mentioned (however I'd assume blender should store all image data in a standarized way)... but actually loading for example 16bpc PNG converts it to float_buffer and stores data like 32bpc (EXR) image. That seems very odd and unconsistent to me. I would expect that all images are loaded to blender to fit the float32 buffer, converting (sRGB->linear) all 8bit images to this format as well. Instead what we have here is two different data formats, one (for 8bit) holds sRGB raw values, the other (for anything above) linear values. Confusing.

EDIT:

Digging through Blender source code. So there are two formats to hold image buffer values ('byte' when referring to 8bpc and 'float' for 32bpc), would be great if someone who actually know the design could answer how does that work.

Here PNGs are loaded to different image buffer type, depending on the PNG bpc.

Ok, my assumptions were correct IMB_imbuf_types.h:



/* pixels */

/** Image pixel buffer (8bit representation):
* - color space defaults to sRGB.
* - alpha defaults to 'straight'.
*/
unsigned int *rect;
/** Image pixel buffer (float representation):
* - color space defaults to 'linear' (rec709).
* - alpha defaults to 'premul'.
* \note May need gamma correction to sRGB when generating 8bit representations.
* \note Formats that support higher more than 8 but channels load as floats.
*/




The question still remains though: why? Wouldn't it be better to convert all images to linear values and single buffer type?

• If I understand your question, it amounts to mixing apples and oranges. Float 32 bit is scene referred in the context of Blender and many other compositing applications, while 8 bit is display referred. Entirely different fish. Mar 9 '17 at 11:03
• Well, actually, it seems it's not that simple: docs.blender.org/manual/en/dev/data_system/files/media/… Internally Blender’s image system supports either: 8 bit per channel (4 x 8 bits). 32 bit float per channel (4 x 32 bits) - using 4x as much memory. Mar 9 '17 at 11:29
• This is exactly what float_buffer parameter does to an image. And from what I discovered... it seems it's like that 8bpc image holds pixel information in sRGB values, while 32bpc holds pixel values in linear values. I know it seems weird (it is, at least for me) but it looks like this is the case: i can convert pixel values from float_buffer=True, to float_buffer=False with thisformula: excamera.com/sphinx/article-srgb.html and then output images look very similar visually. Mar 9 '17 at 11:33
• Google Scene Referred versus display referred. Two totally, utterly, entirely different types of models. Mar 9 '17 at 11:35
• I underestand what's difference between display and the actual color spaces. The issue here is that blender can load images in two ways: docs.blender.org/api/current/… which seem to store color pixel information in two ways. I understand it shouldn't have any connection to sRGB to linear conversion - hence my confusion. Mar 9 '17 at 11:41