Pixel blend mode algorithms (including 'Overlay')
image blend pixel alpha composite algorithm
I didn’t feel like chipping away at my todo list last time I coded, so I decided instead to implement some new graphical features to AdventureFar’s tile engine.Specifically, I wanted to add shadows for any map makers to place on a map. Iended up doing a bit more than that, and added five different ways of blending stuff together, using the same blend modes PaintShopPro or PhotoShop uses.
Previously I had tiles and masks, and I could only apply that mask in one way. Now I can choose how I want the mask to be applied to the tile. I can apply the mask as illumination or as shadow, or I can continue to use it as exclusion to make the tile partially or fully invisible.
Here are pictures of the new blends I added (all images taken from within AdventureFar), as well as the algorithms to implement them. I intend to add additional ones later, just to give map makers more tools to use creatively. Now that I have them in, if I know the proper algorithm it only takes about 5 minutes to add a new blend mode to the game.
In my game, all of this is done only once when the map is loaded, and the blended tile is then cached. There is no run-time cost for these blends except the initial increased load times. If the same mask is blended in the same way to the same tile, then that final image is shared and it doesn’t take any more memory than previously.
I also added the ability to (inside the engine) run the same blend multiple times in a row, as seen here where I am using Multiply blend for five loops:
Overlay blend was the hardest to do, because all the articles I read online, including PaintShopPro’s and Gimp’s stated algorithms, weren’t producing the correct result for me. It turns out you have to apply it per color channel, not per color. The big thing with Overlay is that it darkens and brightens the image, depending on the mask. It’s a mix of both Screen blendand Multiply blend.
Here’s an example: (images stolen from elsewhere on the internet and then implemented in my game)
If the pixel is less than 128, you use something similar to Multiply blend. If it’s greater or equal to 128, you use something similar to Screen blend. However, you do it per color channel of the pixel, not per the average of the pixel, like I was doing (which was an obvious mistake on my part). So on a single pixel you might Multiply the red channel but Screen the green channel, or whatever. This is the proper way to do Overlay blend.
Another part of the confusion is all the articles I read online just say, “if less than 128 use Multiply, otherwise use Screen blend”. This isn’t entirely true – the multiply used in Overlay Blend is different than the regular Multiply blend. They multiply it by 2 afterward, to make it not contrast with the Screen blend as jarringly. They do the same thing with Screen as well. Observe:
If you compare Overlay blend with Multiply or Screen in your image editor of choice(or in the table above), you’ll notice that the Multiply will be almost twice as dark as the dark parts of Overlay, and Screen will also be somewhat brighter in the same spots than Overlay. All the articles I read failed to mention that… =)
The proper Overlay blend mode algorithm like in Photoshop, PaintShopPro, or Gimp, is as follows:
(Warning: Gimp mixes up the name of their ‘Overlay’ blend. Their ‘Overlay’ is misnamed ‘Hardlight’, and their ‘Hardlight' is misnamed ‘Overlay’. This is their mistake, not Photoshop’s or PaintShopPro’s, as you can easily deduce when reading Gimp’s documentation. The ‘Overlay’ I show above is called ‘Hardlight’ in Gimp)
While doing all this, I also realized I was doing alpha transparency wrong when applying masks to tiles.
Here’s what I used to do:
//Bad: int alpha = maskColor.GetAverageValue();
Here’s what I’m now doing:
//Good: int alpha = (tileColor.GetAlpha()* maskColor.GetAverageValue()) / 255;
What was wrong with the previous one? It overwrites the tile’s transparency with the average of the mask. If the tile already has transparency, its transparency is ignored! The new code takes into account the transparency of both the mask and the tile. Observe:
With the bad result, it ignores the tile’s original transparency. If the tile doesn’t have transparency, then the new chunk of code produces the exact same result as the old code.
As an added bonus, mask blending is now stackable just like the other blends. I can run the same mask-blend several times to make the tile increasingly more transparent. (This is actually what led me to discover this mistake in the first place – realizing that consecutive mask blends wouldn’t stack properly,and then further realizing that they wouldn’t even stack on the tile’s original alpha channel).
Resources that helped me: