1

I am trying to add two images together.

enter image description here

enter image description here

When I do so in GIMP, using the 'Addition' layer-mode I get the following desired output. enter image description here

Now, I am trying to execute this in Python, however I can not manage to get it working properly. According to the gimp documentation, as far as I understand the formula for addition is simply the addition of each pixel, set to a max of 255.

Equation for Addition mode: E = min((M+I), 255)

I try to apply this in Python but the output looks different. How can I get the same output in Python as the gimp addition layer blending mode?

My attempt:

# Load in the images
diffuse = cv2.imread('diffuse.png')
reflection = cv2.imread('reflection.png',)
# Convert BGR to RGB
diffuse = cv2.cvtColor(diffuse, cv2.COLOR_BGR2RGB)
reflection = cv2.cvtColor(reflection, cv2.COLOR_BGR2RGB)
# Covnert to uint64 so values can overflow 255
diffuse = diffuse.astype('uint64')
reflection = reflection.astype('uint64')
added = diffuse + reflection # actually add
# clamp values above 255 to be 255
added[added > 255] = 255

# display 
im = Image.fromarray(added.astype('uint8'))
im.save('blended_python.png')

Now, this looks pretty similar but it is actually too bright compared to the GIMP addition. Just open both in a new tab and alt+tab between them and the difference becomes obvious.
enter image description here

How can I get the exact same result as in GIMP? I tried to use Python-fu or gimp batch rendering (as I have thousands of images that I want to blend like this) but I could not get that working, either.

Mitchell van Zuylen
  • 3,905
  • 4
  • 27
  • 64
  • 1
    There are two blend modes in GIMP. Python's output seems to agree with `Percepture`. GIMP's default (`Auto`) must be using `Linear` mode. – Quang Hoang Dec 06 '21 at 04:42
  • Perhaps I did not make myself clear. I am talking about `layer modes`, i.e., "GIMP has thirty-eight layer modes, split up in seven types. [..] Layer modes are also sometimes called 'blending modes'" as per the documentation I linked – Mitchell van Zuylen Dec 06 '21 at 04:49
  • 1
    Sorry, I meant `Blend Space`, not blend modes. You can choose blend space right below `Mode` options. I think they are related to the second note in your linked document *Prior to blending, images have gamma correction removed and are converted from sRGB to linear.*... – Quang Hoang Dec 06 '21 at 04:51
  • 1
    [Here's](https://i.stack.imgur.com/hO7pq.png) how it looks like when choosing the other option. – Quang Hoang Dec 06 '21 at 04:57
  • Thank you. Indeed the difference is caused by the difference in `Blend Space`. However, now my problem remains; how do I this in Python? I will try to figure it out, but any further help is appreciated – Mitchell van Zuylen Dec 06 '21 at 05:04
  • I’m not familiar with the terminology, but this might help https://stackoverflow.com/questions/57033168/how-to-convert-from-srgb-to-linear-srgb-for-computing-the-color-correction-matri – Quang Hoang Dec 06 '21 at 05:07

1 Answers1

1

With help from user Quang Hoang, I learned that GIMP by default has an implicit conversion (sRGB to linear sRGB) of the two layers before applying the addition.

As such, to recreate this behavior in python we need the following functions to convert back and forth from sRGB to linear sRGB. Functions inspired by this question and this answer.

def srgb_to_linsrgb(srgb):
    # takes np array as input in sRGB color space and convert to linear sRGB
    gamma = pow(((srgb + 0.055) / 1.055), 2.4)
    scale = srgb / 12.92
    return np.where (srgb > 0.04045, gamma, scale)


def linsrgb_to_srgb(lin):
    # takes np array as input in linear sRGB color space and convert to sRGB
    gamma =  1.055 * (pow(lin, (1.0 / 2.4))) - 0.055
    scale =  12.92 * lin
    return np.where(lin > 0.0031308,  gamma, scale)

Then we simply add these functions to the existing code from the original question.

# Load in the images
diffuse = cv2.imread('diffuse.png')
reflection = cv2.imread('reflection.png',)

# Convert BGR to RGB
diffuse = cv2.cvtColor(diffuse, cv2.COLOR_BGR2RGB)
reflection = cv2.cvtColor(reflection, cv2.COLOR_BGR2RGB)

# Convert to sRGB in linear space
lin_diffuse = srgb_to_linsrgb(diffuse)
lin_reflection = srgb_to_linsrgb(reflection)

lin_added = lin_diffuse + lin_reflection # actually add
added = linsrgb_to_srgb(lin_added)
added[added > 255] = 255

# # display 
im = Image.fromarray(added.astype('uint8'))
Mitchell van Zuylen
  • 3,905
  • 4
  • 27
  • 64
  • Yup, exactly. Blending is a linear operation, so you almost always want to perform it in a linear space to get a good-looking result. – hobbs Dec 06 '21 at 06:09