I'm experimenting with drawing to a WriteableBitmap assigned to an Image control within WPF and I am finding that the color values are blending with the background color even when the alpha values are set to 0.
I have the following main window
<Window x:Class="WriteableBitmapTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Test" Height="450" Width="800">
<Grid Background="#999">
<Image x:Name="testImage"/>
</Grid>
</Window>
I'm populating the image control with the following code - expecting that setting the alpha byte to 0 would produce nothing visible.
public partial class MainWindow : Window
{
const int Size = 300;
public MainWindow()
{
InitializeComponent();
this.testImage.Source = CreateBitmap();
}
private ImageSource CreateBitmap()
{
var bitmap = new WriteableBitmap(
Size,
Size,
96,
96,
PixelFormats.Pbgra32,
null);
bitmap.Lock();
FillBitmap(bitmap);
bitmap.Unlock();
return bitmap;
}
private void FillBitmap(WriteableBitmap writableBitmap)
{
var stride = writableBitmap.BackBufferStride;
var bytesPerPixel = writableBitmap.Format.BitsPerPixel / 8;
var pixelRows = Size;
var pixels = new byte[pixelRows * stride * bytesPerPixel];
for (int i = 0; i < pixels.Length; i += bytesPerPixel)
{
pixels[i + 0] = 0x00; // B
pixels[i + 1] = 0x00; // G
pixels[i + 2] = 0xff; // R
pixels[i + 3] = 0x00; // A <--- expect the R value to be ignored when rendered.
}
var rect = new Int32Rect(0, 0, Size, Size);
writableBitmap.WritePixels(rect, pixels, stride, 0);
}
}
Instead, I see a red square that appears to have blended with the background color (which I've set to gray).
I'm wondering if there's any trick to control this blending behaviour? or am I expected to factor in the background color if I'm writing straight to the pixel data like this.
Appreciate any insight!
Use PixelFormats.Bgra32
instead of PixelFormats.Pbgra32
, which would require a pre-multiplied R
value of 0
.
From the online documentation:
Gets the Pbgra32 pixel format. Pbgra32 is a sRGB format with 32 bits per pixel (BPP). Each channel (blue, green, red, and alpha) is allocated 8 bits per pixel (BPP). Each color channel is pre-multiplied by the alpha value.
Besides that, your buffer is too large. bitmap.PixelHeight * stride
is a sufficient size, i.e. the number of rows multiplied by the number of bytes per row.
bitmap.Lock()
and bitmap.Unlock()
are also not required for WritePixels
.
Finally, you should not use BackBufferStride
when you write a pixel buffer that contains a consecutive sequence of pixel. For performance reasons BackBufferStride
could have a different value than the actual stride of your source buffer.
private ImageSource CreateBitmap()
{
var bitmap = new WriteableBitmap(
300,
300,
96,
96,
PixelFormats.Bgra32,
null);
FillBitmap(bitmap);
return bitmap;
}
private void FillBitmap(WriteableBitmap bitmap)
{
var bytesPerPixel = 4; // required for code below
var stride = bitmap.PixelWidth * bytesPerPixel;
var pixels = new byte[bitmap.PixelHeight * stride];
for (int i = 0; i < pixels.Length; i += bytesPerPixel)
{
pixels[i + 0] = 0x00; // B
pixels[i + 1] = 0x00; // G
pixels[i + 2] = 0xFF; // R
pixels[i + 3] = 0x00; // A
}
var rect = new Int32Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight);
bitmap.WritePixels(rect, pixels, stride, 0);
}