0

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:

10% opacity
enter image description here

30% opacity
enter image description here

70% opacity
enter image description here

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.

80% opacity
enter image description here

Nicke Manarin
  • 3,026
  • 4
  • 37
  • 79

0 Answers0