Reputation: 10193
I want to be able to blend two or more Color objects. Say I start with a semi-transparent red:
var red = Color.FromArgb(140, 255, 0, 0);
I would then like to blend a semi-transparent green into it:
var green = Color.FromArgb(140, 0, 255, 0);
The colour mixing code snippets that I've come across usually result in a shade of brown here, but what I'm really looking for is an effect like what you'd get if you were to draw one colour over another in (say) Paint.Net, resulting in more of a dark green:-
I'd then like to mix in a third colour, say a semi-transparent blue:
var blue = Color.FromArgb(140, 0, 0, 255);
This time, I would like to end up with the teal-ish colour seen in the centre of this image:
(Again if I try to use the usual code snippets then I usually end up with a grey or brown).
As an aside, it's probably worth mentioning that the following code pretty much achieves what I'm after:
using (var bitmap = new Bitmap(300, 300))
{
using (var g = Graphics.FromImage(bitmap))
{
var c1 = Color.FromArgb(alpha: 128, red: 255, green: 0, blue: 0);
var c2 = Color.FromArgb(alpha: 200, red: 0, green: 255, blue: 0);
var c3 = Color.FromArgb(alpha: 100, red: 0, green: 0, blue: 255);
g.FillRectangle(new SolidBrush(c1), 100, 0, 100, 100);
g.FillRectangle(new SolidBrush(c2), 125, 75, 100, 100);
g.FillRectangle(new SolidBrush(c3), 75, 50, 100, 100);
}
}
I guess it's the code equivalent of my Paint.Net steps, drawing one semi-transparent coloured rectangle over another. However I want to be able to calculate the final blended colour, and use that in one call to g.FillRectangle()
, rather than call the method three times to achieve the blending effect.
Finally, this is an example of the kind of colour mixing code snippet that I referred to earlier, that typically yield shades of brown when I use them for my colours:-
private Color Blend(Color c1, Color c2)
{
var aOut = c1.A + (c1.A * (255 - c1.A) / 255);
var rOut = (c1.R * c1.A + c2.R * c2.A * (255 - c1.A) / 255) / aOut;
var gOut = (c1.G * c1.A + c2.G * c2.A * (255 - c1.A) / 255) / aOut;
var bOut = (c1.B * c1.A + c2.B * c2.A * (255 - c1.A) / 255) / aOut;
return Color.FromArgb(aOut, rOut, gOut, bOut);
}
Upvotes: 3
Views: 2260
Reputation: 10193
Thanks to the earlier comment from @TaW (otherwise I'd never have solved this!), I was able to write a method to blend two colours in the style of Paint.net, Paintshop, etc:-
var r = Color.FromArgb(140, 255, 0, 0);
var g = Color.FromArgb(140, 0, 255, 0);
var b = Color.FromArgb(140, 0, 0, 255);
// How to use:
var rg= AlphaComposite(r, g);
var rb= AlphaComposite(r, b);
var gb= AlphaComposite(g, b);
var rgb= AlphaComposite(AlphaComposite(r, g), b);
...
// A cache of all opacity values (0-255) scaled down to 0-1 for performance
private readonly float[] _opacities = Enumerable.Range(0, 256)
.Select(o => o / 255f)
.ToArray();
private Color AlphaComposite(Color c1, Color c2)
{
var opa1 = _opacities[c1.A];
var opa2 = _opacities[c2.A];
var ar = opa1 + opa2 - (opa1 * opa2);
var asr = opa2 / ar;
var a1 = 1 - asr;
var a2 = asr * (1 - opa1);
var ab = asr * opa1;
var r = (byte)(c1.R * a1 + c2.R * a2 + c2.R * ab);
var g = (byte)(c1.G * a1 + c2.G * a2 + c2.G * ab);
var b = (byte)(c1.B * a1 + c2.B * a2 + c2.B * ab);
return Color.FromArgb((byte)(ar * 255), r, g, b);
}
Upvotes: 2