Edit Raw Pixel Data of Writeablebitmap

Edit raw pixel data of WriteableBitmap?

To answer your question, you can more directly access a writable bitmap's data by using the Lock, write, Unlock pattern, as demonstrated below, but it is typically not necessary unless you are basing your drawing upon the contents of the image. More typically, you can just create a new buffer and make it a bitmap, rather than the other way around.

That being said, there are many extensibility points in WPF to perform innovative drawing without resorting to pixel manipulation. For most controls, the existing WPF primitives (Border, Line, Rectangle, Image, etc...) are more than sufficient - don't be concerned about using many of them, they are rather cheap to use. For complex controls, you can use the DrawingContext to draw D3D primitives. For image effects, you can implement GPU assisted shaders using the Effect class or use the built in effects (Blur and Shadow).

But, if your situation requires direct pixel access, pick a pixel format and start writing. I suggest BGRA32 because it is easy to understand and is probably the most common one to be discussed.

BGRA32 means the pixel data is stored in memory as 4 bytes representing the blue, green, red, and alpha channels of an image, in that order. It is convenient because each pixel ends up on a 4 byte boundary, lending it to storage in an 32 bit integer. When dealing with a 32 bit integer, keep in mind the order will be reversed on most platforms (check BitConverter.IsLittleEndian to determine proper byte order at runtime if you need to support multiple platforms, x86 and x86_64 are both little endian)

The image data is stored in horizontal strips which are one stride wide which compose a single row the width of an image. The stride width is always greater than or equal to the pixel width of the image multiplied by the number of bytes per pixel in the format selected. Certain situations can cause the stride to be longer than the width * bytesPerPixel which are specific to certain architechtures, so you must use the stride width to calculate the start of a row, rather than multiplying the width. Since we are using a 4 byte wide pixel format, our stride does happen to be width * 4, but you should not rely upon it.

As mentioned, the only case I would suggest using a WritableBitmap is if you are accessing an existing image, so that is the example below:

Before / After:

image run through below algorithm

// must be compiled with /UNSAFE
// get an image to draw on and convert it to our chosen format
BitmapSource srcImage = JpegBitmapDecoder.Create(File.Open("img13.jpg", FileMode.Open),
BitmapCreateOptions.None, BitmapCacheOption.OnLoad).Frames[0];

if (srcImage.Format != PixelFormats.Bgra32)
srcImage = new FormatConvertedBitmap(srcImage, PixelFormats.Bgra32, null, 0);

// get a writable bitmap of that image
var wbitmap = new WriteableBitmap(srcImage);

int width = wbitmap.PixelWidth;
int height = wbitmap.PixelHeight;
int stride = wbitmap.BackBufferStride;
int bytesPerPixel = (wbitmap.Format.BitsPerPixel + 7) / 8;

wbitmap.Lock();
byte* pImgData = (byte*)wbitmap.BackBuffer;

// set alpha to transparent for any pixel with red < 0x88 and invert others
int cRowStart = 0;
int cColStart = 0;
for (int row = 0; row < height; row++)
{
cColStart = cRowStart;
for (int col = 0; col < width; col++)
{
byte* bPixel = pImgData + cColStart;
UInt32* iPixel = (UInt32*)bPixel;

if (bPixel[2 /* bgRa */] < 0x44)
{
// set to 50% transparent
bPixel[3 /* bgrA */] = 0x7f;
}
else
{
// invert but maintain alpha
*iPixel = *iPixel ^ 0x00ffffff;
}

cColStart += bytesPerPixel;
}
cRowStart += stride;
}
wbitmap.Unlock();
// if you are going across threads, you will need to additionally freeze the source
wbitmap.Freeze();

However, it really isn't necessary if you are not modifying an existing image. For example, you can draw a checkerboard pattern using all safe code:

Output:

checkerboard pattern at 25 percent zoom

// draw rectangles
int width = 640, height = 480, bytesperpixel = 4;
int stride = width * bytesperpixel;
byte[] imgdata = new byte[width * height * bytesperpixel];

int rectDim = 40;
UInt32 darkcolorPixel = 0xffaaaaaa;
UInt32 lightColorPixel = 0xffeeeeee;
UInt32[] intPixelData = new UInt32[width * height];
for (int row = 0; row < height; row++)
{
for (int col = 0; col < width; col++)
{
intPixelData[row * width + col] = ((col / rectDim) % 2) != ((row / rectDim) % 2) ?
lightColorPixel : darkcolorPixel;
}
}
Buffer.BlockCopy(intPixelData, 0, imgdata, 0, imgdata.Length);

// compose the BitmapImage
var bsCheckerboard = BitmapSource.Create(width, height, 96, 96, PixelFormats.Bgra32, null, imgdata, stride);

And you don't really even need an Int32 intermediate, if you write to the byte array directly.

Output:

Gradient produced from below

// draw using byte array
int width = 640, height = 480, bytesperpixel = 4;
int stride = width * bytesperpixel;
byte[] imgdata = new byte[width * height * bytesperpixel];

// draw a gradient from red to green from top to bottom (R00 -> ff; Gff -> 00)
// draw a gradient of alpha from left to right
// Blue constant at 00
for (int row = 0; row < height; row++)
{
for (int col = 0; col < width; col++)
{
// BGRA
imgdata[row * stride + col * 4 + 0] = 0;
imgdata[row * stride + col * 4 + 1] = Convert.ToByte((1 - (col / (float)width)) * 0xff);
imgdata[row * stride + col * 4 + 2] = Convert.ToByte((col / (float)width) * 0xff);
imgdata[row * stride + col * 4 + 3] = Convert.ToByte((row / (float)height) * 0xff);
}
}
var gradient = BitmapSource.Create(width, height, 96, 96, PixelFormats.Bgra32, null, imgdata, stride);

Edit: apparently, you are trying to use WPF to make some sort of image editor. I would still be using WPF primitives for shapes and source bitmaps, and then implement translations, scaling, rotation as RenderTransform's, bitmap effects as Effect's and keep everything within the WPF model. But, if that does not work for you, we have many other options.

You could use WPF primitives to render to a RenderTargetBitmap which has a chosen PixelFormat to use with WritableBitmap as below:

Canvas cvRoot = new Canvas();
// position primitives on canvas

var rtb = new RenderTargetBitmap(width, height, dpix, dpiy, PixelFormats.Bgra32);
var wb = new WritableBitmap(rtb);

You could use a WPF DrawingVisual to issue GDI style commands then render to a bitmap as demonstrated on the sample on the RenderTargetBitmap page.

You could use GDI using an InteropBitmap created using System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap from an HBITMAP retrieved from a Bitmap.GetHBitmap method. Make sure you don't leak the HBITMAP, though.

How to write pixels in WriteableBitmap by bytes from BitmapImage?

You should use CopyPixels.

MainWindow.xaml:

<Grid>
<Image x:Name="image"></Image>
</Grid>

MainWindow.xaml.cs:

    private void Method()
{
BitmapImage bi = new BitmapImage(new Uri(@"Image.png", UriKind.Relative));

int stride = bi.PixelWidth * (bi.Format.BitsPerPixel + 7) / 8;
byte[] data = new byte[stride * bi.PixelHeight];

bi.CopyPixels(data, stride, 0);

WriteableBitmap wb = new WriteableBitmap(
bi.PixelWidth,
bi.PixelHeight,
bi.DpiX, bi.DpiY,
bi.Format, null);

wb.WritePixels(
new Int32Rect(0, 0, bi.PixelWidth, bi.PixelHeight),
data, stride, 0);

image.Source = wb; // an Image class instance from XAML.
}

How to improve performance of Changing Image Pixel colour using WriteableBitmapEx in Windows 10?

After doing so much trial and error methods, I found BitmapIcon control which is available for Windows Phone 8.1/Windows 10. It has a foreground property which can be used to change icon color. I used it for following types of icons and the performance was really good and not much code needed. The best part is no third party API required.

Icon

BitmapIcon Reference
BitmapIcon

How to change value of red/green/blue/alpha values from pixels

Assuming that you already have a BitmapSource (or an instance of a derived class like BitmapImage) in a source valriable, you would first create a BitmapSource with a known pixel format, and then create a WritabelBitmap from that:

// source is a BitmapSource with whatever PixelFormat
var format = PixelFormats.Bgra32;
var temp = new FormatConvertedBitmap(source, format, null, 0); // force BGRA
var bitmap = new WriteableBitmap(temp);

Now you could either directly manipulate the data in the WriteableBitmap's BackBuffer property, or first do it a bit simpler and create a copy of the buffer like this:

var width = bitmap.PixelWidth;
var height = bitmap.PixelHeight;
var stride = bitmap.BackBufferStride;
var buffer = new byte[stride * height];
bitmap.CopyPixels(buffer, stride, 0);

Then manipulate the buffer and write it back to the WriteableBitmap. Since you have specified the PixelFormat, you know which byte of each 4-byte pixel value is B, G, R and A respectively.

for (int i = 0; i < buffer.Length; i += 4)
{
// Blue
//buffer[i + 0] = ...

// Green
buffer[i + 1] = (byte)Math.Max(buffer[i + 1] - 30, 0);

// Red
buffer[i + 2] = (byte)Math.Max(buffer[i + 2] - 30, 0);

// Alpha
//buffer[i + 3] = ...
}

bitmap.WritePixels(new Int32Rect(0, 0, width, height), buffer, stride, 0);

When you have assigned the WriteableBitmap to the Source property of an Image element, or the ImageSource property of an ImageBrush, you can call WritePixels later (and as often as necessary) without needing to reassign it it to the Source/ImageSource property. It is changed "in place".

get image pixels into array

In order to get the bitmap's raw pixel data you may use one of the BitmapSource.CopyPixels methods, e.g. like this:

var bytesPerPixel = (source.Format.BitsPerPixel + 7) / 8;
var stride = source.PixelWidth * bytesPerPixel;
var bufferSize = source.PixelHeight * stride;
var buffer = new byte[bufferSize];
source.CopyPixels(buffer, stride, 0);

Writing to a WriteableBitmap can be done by one of its WritePixels methods.

Alternatively you may access the bitmap buffer by the WriteableBitmap's BackBuffer property.

For converting a bitmap to grayscale, you might use a FormatConvertedBitmap like this:

var grayscaleBitmap = new FormatConvertedBitmap(source, PixelFormats.Gray8, null, 0d);

Why AccessViolationException occurs when accessing pixels in WriteableBitmap Image?

I ended up using the answer of this post. I now can edit raw pixel data of any WriteableBitmap image before sending it to image control in WPF. Below is what I exactly used but here I just convert every frame to some transparency under a condition:

        public void ConvertImage(ref WriteableBitmap Wbmp)
{
int width = Wbmp.PixelWidth;
int height = Wbmp.PixelHeight;
int stride = Wbmp.BackBufferStride;
int bytesPerPixel = (Wbmp.Format.BitsPerPixel + 7) / 8;

unsafe
{
byte* pImgData = (byte*)Wbmp.BackBuffer;

// set alpha to transparent for any pixel with red < 0x88 and invert others
int cRowStart = 0;
int cColStart = 0;
for (int row = 0; row < height; row++)
{
cColStart = cRowStart;
for (int col = 0; col < width; col++)
{
byte* bPixel = pImgData + cColStart;
UInt32* iPixel = (UInt32*)bPixel;

if (bPixel[2 /* bgRa */] < 0x44)
{
// set to 50% transparent
bPixel[3 /* bgrA */] = 0x7f;
}
else
{
// invert but maintain alpha
*iPixel = *iPixel ^ 0x00ffffff;
}

cColStart += bytesPerPixel;
}
cRowStart += stride;
}
}
}

And the routine of using it is like this:

        masterImage.Lock();
ConvertImage(ref masterImage);
masterImage.AddDirtyRect(new Int32Rect(0, 0, masterImage.PixelWidth, masterImage.PixelHeight));
masterImage.Unlock();

How to create a BitmapImage from a pixel byte array (live video display)

This should do the trick. it's super fast.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

using System.Drawing;
using System.Runtime.InteropServices;
using System.IO;
using System.ComponentModel;

public class MakeBitmapSource
{
[DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory")]
public static extern void CopyMemory(IntPtr Destination, IntPtr Source, uint Length);

public static BitmapSource FromNativePointer(IntPtr pData, int w, int h, int ch)
{
PixelFormat format = PixelFormats.Default;

if (ch == 1) format = PixelFormats.Gray8; //grey scale image 0-255
if (ch == 3) format = PixelFormats.Bgr24; //RGB
if (ch == 4) format = PixelFormats.Bgr32; //RGB + alpha

WriteableBitmap wbm = new WriteableBitmap(w, h, 96, 96, format, null);
CopyMemory(wbm.BackBuffer, pData, (uint)(w * h * ch));

wbm.Lock();
wbm.AddDirtyRect(new Int32Rect(0, 0, wbm.PixelWidth, wbm.PixelHeight));
wbm.Unlock();

return wbm;
}

public static BitmapSource FromArray(byte[] data, int w, int h, int ch)
{
PixelFormat format = PixelFormats.Default;

if (ch == 1) format = PixelFormats.Gray8; //grey scale image 0-255
if (ch == 3) format = PixelFormats.Bgr24; //RGB
if (ch == 4) format = PixelFormats.Bgr32; //RGB + alpha

WriteableBitmap wbm = new WriteableBitmap(w, h, 96, 96, format, null);
wbm.WritePixels(new Int32Rect(0, 0, w, h), data, ch * w, 0);

return wbm;
}
}


Related Topics



Leave a reply



Submit