I have a byte[]
containing pre-multiplied pixels (it's a cursor image, arrow) and I want to write/merge into a canvas which contains pixels with non-multiplied alpha. But I also want to control the opacity of the cursor being merged.
Previously, my code looked like this:
var canvas = [...];
var cursor = [...];
var alpha = cursor[bufferIndex + 3] + 1;
if (alpha == 1)
continue;
//Pre-multiplied alpha values.
var invAlpha = 256 - alpha;
alpha += 1;
var b = (byte)((alpha * cursor[bufferIndex] + invAlpha * canvas[canvasIndex]) >> 8);
var g = (byte)((alpha * cursor[bufferIndex + 1] + invAlpha * canvas[canvasIndex + 1]) >> 8);
var r = (byte)((alpha * cursor[bufferIndex + 2] + invAlpha * canvas[canvasIndex + 2]) >> 8);
var a = (byte)(canvas[canvasIndex + 3] + (alpha * (255 - canvas[canvasIndex + 3]) / 255));
Imagine now that I have an opacity
ranging from 0 to 1.
It would be just a matter of multiplying alpha to opacity, right?
255 * 0.5 ~ 128
But it's not right, see examples:
It's simply darkening the cursor, instead of alpha-blending into the canvas. Another important detail is that the canvas itself may have its transparency channel different than 255.
A (somewhat) working code
var topAlpha = cursor[bufferIndex + 3];
if (topAlpha == 0)
continue;
var topBlue = cursor[bufferIndex];
var topGreen = cursor[bufferIndex + 1];
var topRed = cursor[bufferIndex + 2];
//C = 255.0 * PC / A + 0.5
var b = (byte)(255.0f * topBlue / topAlpha + 0.5f);
var g = (byte)(255.0f * topGreen / topAlpha + 0.5f);
var r = (byte)(255.0f * topRed / topAlpha + 0.5f);
//Calculate the index of the pixel in the canvas data array.
var canvasIndex = (canvasRow - topOffset) * canvasStride + (canvasCol - leftOffset) * ChannelCount;
//The pixels should blend in relation to their opacities.
topAlpha = (byte)(topAlpha * Opacity);
int bottomAlpha = canvas[canvasIndex + 3];
//Blue = (topBlue * topA / 255) + (bottomBlue * bottomA * (255 - topa) / (255 * 255))
canvas[canvasIndex] = (byte)((b * topAlpha / 255) + (canvas[canvasIndex] * bottomAlpha * (255 - topAlpha)) / (255 * 255));
//Green = (topGreen * v / 255) + (bottomGreen * bottomA * (255 - topa) / (255 * 255))
canvas[canvasIndex + 1] = (byte)((g * topAlpha / 255) + (canvas[canvasIndex + 1] * bottomAlpha * (255 - topAlpha)) / (255 * 255));
//Red = (topRed * topA / 255) + (bottomRed * bottomA * (255 - topa) / (255 * 255))
canvas[canvasIndex + 2] = (byte)((r * topAlpha / 255) + (canvas[canvasIndex + 2] * bottomAlpha * (255 - topAlpha)) / (255 * 255)));
//Alpha = topA + (bottomA * (255 - topa) / 255)
canvas[canvasIndex + 3] = (byte)(topAlpha + (bottomAlpha * (255 - topAlpha) / 255)));
The issue now is that the borders of the cursor are not properly anti-aliased.