Как отображать необработанные данные в виде изображения (Visual Studio С#)

Я получаю некоторые необработанные данные, которые будут храниться в байтовом массиве, где каждый 2 байта является значением пикселя (16 бит /px ). Для начала, массив будет содержать 100x100 * 2 байта (достаточно для изображения размером 100x100 пикселей). Я хотел бы отобразить эти данные в окне формы. В конце концов, я хотел бы обновить изображение с новыми данными, чтобы он выглядел как видеопоток. Не требуется строгая частота кадров. Как это может быть сделано? Любые примеры кода на С#?

EDIT: После некоторых предложений и обзоров десятков подобных вопросов я до сих пор не могу этого добиться. Вот общая идея того, что я пытаюсь сделать, но изображение не отображается в окне изображения на форме. Что конкретно неправильно с моей реализацией и как ее исправить?

// array of data I collected
byte[] dataArray = new byte[100 * 100 * 2]; 
//create a pointer to the data
IntPtr hglobal = Marshal.AllocHGlobal(100 * 100 * 2);

// copy my array to global
Marshal.Copy(dataArray, 0, hglobal, dataArray.Length);
// create a bitmap: 100x100 pixels, 2bytes/pixel, 16bitgrayscale
Bitmap newBitmap = new Bitmap(100, 100, 2 * 100, PixelFormat.Format16bppGrayScale, hglobal);

// display bitmap
pictureBox1.Image = newBitmap;

// free the memory
Marshal.FreeHGlobal(hglobal);

Ответ 1

Основная проблема заключается в том, что PixelFormat.Format16bppGrayScale не поддерживается (по крайней мере, в моей системе Win 8.1 x64). Поэтому перед отображением нужно преобразовать изображение в rgb:

private void Form1_Load(object sender, EventArgs e)
{
    //Create pixel data to put in image, use 2 since it is 16bpp
    Random r = new Random();
    int width = 100;
    int height = 100;
    byte[] pixelValues = new byte[width * height * 2];
    for (int i = 0; i < pixelValues.Length; ++i)
    {
        // Just creating random pixel values for test
        pixelValues[i] = (byte)r.Next(0, 256);
    }

    var rgbData = Convert16BitGrayScaleToRgb48(pixelValues, width, height);
    var bmp = CreateBitmapFromBytes(rgbData, width, height);

    // display bitmap
    pictureBox1.Image = bmp;
}

private static byte[] Convert16BitGrayScaleToRgb48(byte[] inBuffer, int width, int height)
{
    int inBytesPerPixel = 2;
    int outBytesPerPixel = 6;

    byte[] outBuffer = new byte[width * height * outBytesPerPixel];
    int inStride = width * inBytesPerPixel;
    int outStride = width * outBytesPerPixel;

    // Step through the image by row
    for (int y = 0; y < height; y++)
    {
        // Step through the image by column
        for (int x = 0; x < width; x++)
        {
            // Get inbuffer index and outbuffer index
            int inIndex = (y * inStride) + (x * inBytesPerPixel);
            int outIndex = (y * outStride) + (x * outBytesPerPixel);

            byte hibyte = inBuffer[inIndex + 1];
            byte lobyte = inBuffer[inIndex];

            //R
            outBuffer[outIndex] = lobyte;
            outBuffer[outIndex + 1] = hibyte;

            //G
            outBuffer[outIndex + 2] = lobyte;
            outBuffer[outIndex + 3] = hibyte;

            //B
            outBuffer[outIndex + 4] = lobyte;
            outBuffer[outIndex + 5] = hibyte;
        }
    }
    return outBuffer;
}

private static Bitmap CreateBitmapFromBytes(byte[] pixelValues, int width, int height)
{
    //Create an image that will hold the image data
    Bitmap bmp = new Bitmap(width, height, PixelFormat.Format48bppRgb);

    //Get a reference to the images pixel data
    Rectangle dimension = new Rectangle(0, 0, bmp.Width, bmp.Height);
    BitmapData picData = bmp.LockBits(dimension, ImageLockMode.ReadWrite, bmp.PixelFormat);
    IntPtr pixelStartAddress = picData.Scan0;

    //Copy the pixel data into the bitmap structure
    System.Runtime.InteropServices.Marshal.Copy(pixelValues, 0, pixelStartAddress, pixelValues.Length);

    bmp.UnlockBits(picData);
    return bmp;
}

Идея была взята из этот поток.

Ответ 2

Используйте этот Bitmap конструктор:

public Bitmap(
    int width,
    int height,
    int stride,
    PixelFormat format,
    IntPtr scan0
)

Вы передаете ему форму вашего растрового изображения, шаг (количество байтов на строку, включая дополнение), формат пикселя и данные пикселя в качестве указателя void *. Вы можете создать последнее с Marshal.AllocHGlobal и заполнить его как обычно с помощью операций указателя. Не забудьте освободить эту память после создания растрового изображения.

Изменить учетную запись для обновленного вопроса:

Просто позвоните IntPtr.ToPointer(), чтобы вернуть указатель. Если вы знакомы с C, остальное должно быть торт:

var p=(char *)hglobal.ToPointer();  // bad name by the way, it not a handle, it a pointer
p[0]=0;                             // access it like any normal pointer

Однако вы можете использовать Marshaller для копирования памяти для вас из управляемого в неуправляемый (получение грязных рук обычно нахмуривается на С#):

Marshal.Copy(dataArray, 0, hglobal, dataArray.Length); // again, terrible name

A Bitmap - это Image (как и в нем, он вытекает из него), однако вы используете Graphics.DrawImage() неправильно. Как говорит ошибка, это не статический метод, вы рисуете его в определенном графическом контексте. Теперь, каков этот графический контекст, что до вас:

  • Если вы хотите нарисовать его в ответ на WM_PAINT, используйте событие Paint - он предоставляет вам специальный объект Graphics, настроенный с отсечением, и все, как указано в системе окон.
  • Если вы хотите покрасить его в растровое изображение, которое впоследствии будет отображаться (обычное использование, также называемое двойной буферизацией), используйте Graphics.FromImage() в исходном растровом изображении, затем нарисуйте его растровое изображение.

Вы можете (и должны) удалить буфер виртуальной памяти, как только получите результат от конструктора Bitmap. Не просачивайте память, используйте конструкцию try..finally.