0

I have a few small WinForms applications written in C# that utilise System.Drawing.Bitmap and System.Windows.Forms.PictureBox to display a bitmap from a raw byte buffer that the application maintains over time. In a way, it's akin to poor-man's animation.

I would like to port these applications to WPF but there's a lot of custom logic in the byte buffer writing that I am unwilling to change. After some Googling, I tried using the System.Windows.Media.Imaging.WriteableBitmap class but couldn't make it work. How is this class supposed to be used and does it fit with my original design?

Here's a snippet from an original WinForms app that cumulatively writes random colors into random positions on a bitmap:

private void Form1_Load(object sender, EventArgs e)
{
    _timer.Tick += new EventHandler(Timer_Tick);
    _timer.Interval = 25;
    _timer.Enabled = true;
}

private void Timer_Tick(object sender, EventArgs e)
{
    Animate();
}

private void Animate()
{
    Bitmap bitmap = (Bitmap)pictureBox1.Image;

    if (bitmap != null)
    {
        bitmap.Dispose();
    }

    bitmap = new Bitmap(pictureBox1.Width, pictureBox1.Height);

    var data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);

    if (_buffer == null)
    {
        _buffer = new byte[data.Stride * bitmap.Height];
    }

    for (int i = 0; i < 1000; i++)
    {
        var x = _random.Next(bitmap.Width);
        var y = _random.Next(bitmap.Height);

        var red = (byte)_random.Next(byte.MaxValue);
        var green = (byte)_random.Next(byte.MaxValue);
        var blue = (byte)_random.Next(byte.MaxValue);
        var alpha = (byte)_random.Next(byte.MaxValue);

        _buffer[y * data.Stride + x * 4] = blue;
        _buffer[y * data.Stride + x * 4 + 1] = green;
        _buffer[y * data.Stride + x * 4 + 2] = red;
        _buffer[y * data.Stride + x * 4 + 3] = alpha;
    }

    Marshal.Copy(_buffer, 0, data.Scan0, _buffer.Length);

    bitmap.UnlockBits(data);

    pictureBox1.Image = bitmap;
}
Jono
  • 1,964
  • 4
  • 18
  • 35
  • 1
    Use the (well documented) [`BitmapSource.Create`](https://msdn.microsoft.com/en-us/library/ms616045(v=vs.110).aspx) method. Assign the BitmapSource to the `Source` property of an [`Image`](https://msdn.microsoft.com/en-us/library/system.windows.controls.image(v=vs.110).aspx) control. – Clemens Dec 30 '16 at 14:39
  • 1
    I was going to post an answer on how to do this using `WriteableBitmap` but the question was closed. FWIW, I put it here: http://pastebin.com/Ufmx4giD – 001 Dec 30 '16 at 14:44
  • Thank you, @Clemens. Your concise answer helped me find my feet. – Jono Dec 31 '16 at 20:57

1 Answers1

4

If you already have existing code, I think maybe the easiest and fastest (not processing fastest though) would be to maintain your code and convert to a BitmapSource when you need to display the image:

  BitmapImage bmpImage = new BitmapImage();
  MemoryStream stream = new MemoryStream();
  bitmap.Save(stream, ImageFormat.MemoryBmp);
  bmpImage.BeginInit();
  bmpImage.StreamSource = stream;
  bmpImage.EndInit();
  bmpImage.Freeze();

  return bmpImage;

Another option, which will be more efficient but might need you to port more code, is to use the WritableBitmap. You can create the WritableBitmap in the same manner you did with your Bitmap object, and you have access to its raw backing buffer (and IntPtr) and you can manipulate the image data as you wish. This link should give you all the information you need about WritableBitmap: WriteableBitmap

Here is an example for your case (note you need to know your dpi):

      WriteableBitmap bitmap = new WriteableBitmap(pictureBox1.Width, pictureBox1.Height, dpi, dpi, PixelFormats.Bgra32, null);

  if (_buffer == null)
  {
    _buffer = new byte[bitmap.BackBufferStride * pictureBox1.Height];
  }

  for (int i = 0; i < 1000; i++)
  {
    var x = _random.Next(bitmap.Width);
    var y = _random.Next(bitmap.Height);

    var red = (byte)_random.Next(byte.MaxValue);
    var green = (byte)_random.Next(byte.MaxValue);
    var blue = (byte)_random.Next(byte.MaxValue);
    var alpha = (byte)_random.Next(byte.MaxValue);

    _buffer[y * bitmap.BackBufferStride + x * 4] = blue;
    _buffer[y * bitmap.BackBufferStride + x * 4 + 1] = green;
    _buffer[y * bitmap.BackBufferStride + x * 4 + 2] = red;
    _buffer[y * bitmap.BackBufferStride + x * 4 + 3] = alpha;
  }

  bitmap.WritePixels(new System.Windows.Int32Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight),
      _buffer, bitmap.PixelWidth * bitmap.Format.BitsPerPixel / 8, 0);
Radek Secka
  • 318
  • 2
  • 11
IProgrammer
  • 321
  • 1
  • 2
  • 8
  • Thank you. I tried both your and Clemens' suggestions (WriteableBitmap and BitmapSource.Create, respectively) and both work for me. I tried to change your code sample to use PixelFormats.Bgra32 - with an s - but my edit wasn't long enough for stackoverflow to accept my proposal. I didn't try your initial suggestion at all - I think it relates to one of the classes that confused the (*&%^ out of me in the first place. :) – Jono Dec 31 '16 at 21:02
  • I made that change Jono proposed. – Radek Secka May 31 '18 at 15:46