flrs / blend_modes

Python package that implements image blend modes
MIT License
140 stars 28 forks source link

Is the Overlay Blending Mode Implemented Correctly? #6

Closed JohnTravolski closed 5 years ago

JohnTravolski commented 6 years ago

It seems the the overlay blending mode produces the exact same output as the soft light blending mode, or at least it does in all of my tests. Are you confident that it's implemented correctly?

For reference, in Photoshop and After Effects, soft light and overlay look noticeably different.

flrs commented 6 years ago

Hi @JohnTravolski,

No definitive answers yet for now. But there is a possibility that soft light and overlay are implemented differently in the Adobe world and in GIMP. If the tests of this package pass it means that the method implementation in blend_modes matches GIMP's methods.

Let me know if you find out more!

JohnTravolski commented 5 years ago

I found an interesting line here: https://docs.gimp.org/en/gimp-concepts-layer-modes.html

"In some versions of GIMP, “Overlay” mode and “Soft light” mode are identical."

This is reinforced here: https://gimpchat.com/viewtopic.php?f=28&t=6248#p77599

In the images displayed in the first link, the pictures for soft light and overlay appear to be identical, so I'm wondering in what versions of GIMP they are different. It seems kind of pointless to have two identical blending modes with different names. Were the equations in the first link the ones you used to implement the blending modes?

EDIT:

I went through your source code and verified that your implementation of soft light and overlay are indeed identical.

Overlay uses: comp = img_in[:,:,:3] * (img_in[:,:,:3] + (2 * img_layer[:,:,:3]) * (1 - img_in[:,:,:3])) Soft light uses:

comp = (1.0 - img_in[:, :, :3]) * img_in[:, :, :3] * img_layer[:, :, :3] \
           + img_in[:, :, :3] * (1.0 - (1.0-img_in[:, :, :3])*(1.0-img_layer[:, :, :3]))

Here's the algebraic simplification:

6kroe09

JohnTravolski commented 5 years ago

I took a look at Wikipedia's implementation of Overlay and used your code as a basis to implement it. For example, given your code, in the overlay function definition, if I replace: comp = img_in[:,:,:3] * (img_in[:,:,:3] + (2 * img_layer[:,:,:3]) * (1 - img_in[:,:,:3]))

with (using the Wikipedia formula):

a = img_in[:,:,:3]
b = img_layer[:,:,:3]
comp = np.less(a, 0.5)*(2*a*b) + np.greater_equal(a, 0.5)*(1 - (2*(1 - a)*(1 - b)))

Then the overlay blending mode gives a result that is indeed different from Soft Light and looks just like the results that After Effects gives me. For my purposes, this is the version of Overlay that I prefer, even if it's not what GIMP uses.

JohnTravolski commented 5 years ago

You mentioned that these could be sped up if implemented in Cython. If you're interested, I have done so here, and gotten a pretty good speed improvement: https://pastebin.com/fAD9avjP If there're any obvious optimizations that I'm not seeing in the Cython code, feel free to let me know. I have successfully implemented all of Photoshop's blending modes, as well as yours, except the "Darker Color, Lighter Color, Hue, Saturation, Color, and Luminosity" blending modes (from Photoshop). I attempted hue, but couldn't quite figure it out; you'll see remnants of my attempt in the code.

The only implementation difference from yours is that each blend mode function already expects the input image to be divided by 255, and the output image is not multiplied by 255. This makes it easier to blend multiple layers together without unnecessary computation. So you have to do those division and multiplication steps manually before and after blending. They also don't require the input images to have an alpha channel (but it still works if either do).

JohnTravolski commented 5 years ago

Updated version that runs faster (about a 20% speedup) for 8K images (uses fused types instead of dereferencing a function pointer each time): https://pastebin.com/N3Siqg4b Also, it more closely mimics Photoshop behavior in that it leaves the blend layer as-is when the alpha of the base layer is 0 (instead of just using the alpha of the base layer). Also, when both layers have alpha, the new alpha is computed as a + b - a*b.

flrs commented 5 years ago

@JohnTravolski, thanks for doing work on the overlay code. I will implement it!

flrs commented 5 years ago

@JohnTravolski awesome work with the Cython implementation! I think this is something that others could profit from and we should make it part of the package.

flrs commented 5 years ago

Closed in favor of issues #8 and #9.

AeroDEmi commented 1 year ago

How can I use this Cython implementation?