1

I'm working with WriteableBitmap (PixelFormats.Bgr32). This is the format I need to save for the rest of the program to work.

Writable = new WriteableBitmap(inImage.XSize, inImage.YSize, dpi, dpi, PixelFormats.Bgr32, null);

The image from the device comes in grayscaled, (ushort[] Gray16). To use this image in my program I use the following code (inImage - received image, ImageData = ushort[]):

int[] pixels = Array.ConvertAll(inImage.ImageData, val => checked((int)val));
Writable.WritePixels(new Int32Rect(0, 0, width, height), pixels, width * 4, 0);

If I just use

Writable.WritePixels(new Int32Rect(0, 0, width, height), inImage.ImageData, width * 4, 0);  

I get a message about insufficient buffer size. (System.ArgumentException: "Buffer size is not sufficient." )

Code

int[] pixels = Array.ConvertAll(inImage.ImageData, val => checked((int)val))

or

int[] pixels = new int[inImage.XSize * inImage.YSize];

            for (int i = 0; i < inImage.ImageData.Length; i++)
            {
                pixels[i] = inImage.ImageData[i];
            }

This seems to me to be a very long and slow and unoptimized process.

Is there any way to optimize the process and immediately write ushort[] array to WriteableBitmap (Bgr32) without converting it to int[] ?

EDIT***

I need this code to be able to work with images with a resolution of 4300x4300 with 45 FPS. Below is the full code how I load images (this code is called 45 times per second to load a new image)

public class ImageStruct
        {
            public ushort[] ImageData;
            public int XSize;
            public int YSize;
            public int XDpi;
            public int YDpi;

        }


WriteableBitmap Writeable;
public void LoadFrame(ImageStruct inImage)
{
    var width = inImage.XSize;
    var height = inImage.YSize;
    var destBpp = PixelFormats.Bgr32.BitsPerPixel / 8;
    var uPixels = inImage.ImageData;

    fluoroWritable = new WriteableBitmap(inImage.XSize, inImage.YSize, 96, 96, PixelFormats.Bgr32, null);
    var iPixels = Array.ConvertAll(uPixels, val => checked((int)val));

    fluoroWritable.WritePixels(new Int32Rect(0, 0, width, height), iPixels, width * 4, 0);
}

This code works well when FPS is ~10, If you increase the FPS, it starts to freeze. Profiling shows that it is the moment when I make from ushort[] => int[] takes the longest time.

I tried to use the code suggested in the answer below:

unsafe public void LoadFrameWithLock(ImageStruct inImage)
        {
            var width = inImage.XSize;
            var height = inImage.YSize;
            var destBpp = PixelFormats.Bgr32.BitsPerPixel / 8;
            var uPixels = inImage.ImageData;

            fluoroWritable = new WriteableBitmap(inImage.XSize, inImage.YSize, 96, 96, PixelFormats.Bgr32, null);

            fluoroWritable.Lock();
                    
            int* outputIntValues = (int*)fluoroWritable.BackBuffer;
            ushort[] inputShortValues = inImage.ImageData;
        
            for (int i = 0; i < inputShortValues.Length; i++)
            {               
                byte as8bpp = (byte)(inputShortValues[i] >> 8);
                outputIntValues[i] = /*B*/ as8bpp | /*G*/ (as8bpp << 8) | /*R*/ (as8bpp << 16);
            }           
            fluoroWritable.AddDirtyRect(new Int32Rect(0, 0, fluoroWritable.PixelWidth, fluoroWritable.PixelHeight));

            fluoroWritable.Unlock();
        }

But the FPS with this code is even lower and in addition the WPF application has a completely hanging interface. and images have no grayscale, but are completely black and white (white or black pixels).

I need the BGR32 format because this WritableBitmap (WPF Image object) is then overlaid with shader effects, and I can also get color images in another place.

1 Answers1

0

Is there any way to optimize the process and immediately write ushort[] array to WriteableBitmap (Bgr32) without converting it to int[] ?

Yes, instead of calling WritePixels use the Lock method so the BackBuffer will be available as a naked pointer (do not forget to call Unlock when you are finished).

Please note though that as your source and target pixel formats are different (16bpp grayscale vs. 32bpp BGRx) simple casting of short values to int will not be correct. All 16 bit grayscale values must be converted to 8 bit RGB values:

// the result as you defined in OP
Writable = new WriteableBitmap(inImage.XSize, inImage.YSize, dpi, dpi, PixelFormats.Bgr32, null);

// you must be in an unsafe scope to use pointers
int* outputIntValues = (int*)Writable.BackBuffer;
short[] inputShortValues = inImage.ImageData;

// converting pixels to BGR32
for (int i = 0; i < inputShortValues.Length; i++)
{
    // taking the most significant bits from the 16bpp gray values
    byte as8bpp = (byte)(inputShortValues[i] >> 8);
    outputIntValues[i] = /*B*/ as8bpp | /*G*/ (as8bpp << 8) | /*R*/ (as8bpp << 16);
}

// notifying the consumers that we edited the raw content
Writable.AddDirtyRect(new Int32Rect(0, 0, Writable.PixelWidth, Writable.PixelHeight));

// releasing the buffer
Writable.Unlock();

The sample above needs to be inside of an unsafe scope because of the pointer and you must enable unsafe blocks for your project in your .csproj file.

To make things simpler you can use my Drawing Libraries, which now has dedicated WPF support so the conversion will just be literally two lines. It does not need unsafe context and is actually faster than the example above because it uses parallel processing:

// Interpret your short[] as a grayscale bitmap
using var bmpGrayscale = BitmapDataFactory.CreateBitmapData(buffer: inImage.ImageData,
    size: new Size(inImage.XSize, inImage.XSize),
    stride: inImage.XSize * 2, // if input pixels are contiguous
    KnownPixelFormat.Format16bppGrayScale));

// Convert it to a BGR32 WriteableBitmap (async overloads are available, too)
WriteableBitmap bmpResult = bmpGrayscale.ToWriteableBitmap(PixelFormats.Bgr32);
György Kőszeg
  • 17,093
  • 6
  • 37
  • 65
  • I made your example, and added new details at the top of the question. I am using NetFramework 4.6.2, and cannot use using as your example using KGySoft library – Евгений Федоров Nov 05 '22 at 04:56
  • The library supports .NET Framework back to version 3.5 so it should be alright. If the issue is with the `size` parameter, then try using `new System.Drawing.Size(...)`. The primitive types of `System.Drawing` are available on all frameworks in all environments without installing external packages (not even `System.Drawing.Common`, which is required only for the GDI+ types on .NET Core and above). – György Kőszeg Nov 05 '22 at 12:27