wpfwriteablebitmap

WriteableBitmap blending oddly with background grid


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).

light red square over grey background

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!


Solution

  • 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);
    }