Как скопировать изображение без использования буфера обмена?

Вопрос: У меня есть приведенный ниже код для захвата изображения с веб-камеры.

Моя проблема в этой части:

SendMessage(hCaptureWnd, WM_CAP_COPY, 0, 0);                    // copy it to the clipboard

Что он делает, так это копирует изображение из окна в буфер обмена, а затем создает из него массив байтов.

Это работает, пока вы не используете буфер обмена во время работы программы.
Проблема в том, что это не работает даже для меня, так как я иногда копирую что-то, в то время как Visual Studio занимает целую вечность, чтобы начать отладку. веб-приложение, а затем происходит сбой.

Итак, вот мой вопрос:
Как я могу получить изображение без использования буфера обмена? Или, точнее, как преобразовать hCaptureWnd в System.Drawing.Image?


-- Редактировать:
Я пропустил, чтобы сказать "не создавая файл, я хочу массив байтов".
Это веб-приложение, поэтому пользователь, под которым работает приложение, не должен был писать доступ к файловой системе (запись в файл только для временного тестирования) ...
-- End Edit:


/// <summary>
/// Captures a frame from the webcam and returns the byte array associated
/// with the captured image
/// </summary>
/// <param name="connectDelay">number of milliseconds to wait between connect 
/// and capture - necessary for some cameras that take a while to 'warm up'</param>
/// <returns>byte array representing a bitmp or null (if error or no webcam)</returns>
private static byte[] InternalCaptureToByteArray(int connectDelay = 500)
{
    Clipboard.Clear();                                              // clear the clipboard
    int hCaptureWnd = capCreateCaptureWindowA("ccWebCam", 0, 0, 0,  // create the hidden capture window
        350, 350, 0, 0);
    SendMessage(hCaptureWnd, WM_CAP_CONNECT, 0, 0);                 // send the connect message to it
    Thread.Sleep(connectDelay);                                     // sleep the specified time
    SendMessage(hCaptureWnd, WM_CAP_GET_FRAME, 0, 0);               // capture the frame
    SendMessage(hCaptureWnd, WM_CAP_COPY, 0, 0);                    // copy it to the clipboard
    SendMessage(hCaptureWnd, WM_CAP_DISCONNECT, 0, 0);              // disconnect from the camera
    Bitmap bitmap = (Bitmap)Clipboard.GetDataObject().GetData(DataFormats.Bitmap);  // copy into bitmap

    if (bitmap == null)
        return null;

    using (MemoryStream stream = new MemoryStream())
    {
        bitmap.Save(stream, ImageFormat.Bmp);    // get bitmap bytes
        return stream.ToArray();
    } // End Using stream

} // End Function InternalCaptureToByteArray

Примечание (http://msdn.microsoft.com/en-us/library/windows/desktop/dd756879(v=vs.85).aspx):

HWND VFWAPI capCreateCaptureWindow(
  LPCTSTR lpszWindowName,
  DWORD dwStyle,
  int x,
  int y,
  int nWidth,
  int nHeight,
  HWND hWnd,
  int nID
);


#define VFWAPI  WINAPI 

typedef HANDLE HWND;
typedef PVOID HANDLE;
typedef void *PVOID;

Полный код для справки

using System;
using System.IO;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
using System.Drawing.Imaging;
using System.Collections.Generic;
using System.Runtime.InteropServices;


// http://www.creativecodedesign.com/node/66
// http://www.barebonescoder.com/2012/01/finding-your-web-cam-with-c-directshow-net/
// http://www.codeproject.com/Articles/15219/WebCam-Fast-Image-Capture-Service-using-WIA
// http://www.c-sharpcorner.com/uploadfile/yougerthen/integrate-the-web-webcam-functionality-using-C-Sharp-net-and-com-part-viii/
// http://forums.asp.net/t/1410057.aspx


namespace cc.Utility
{


    // bool isCaptured = ccWebCam.CaptureSTA("capture.jpg"); // Access to path C:\Program Files (x86)\Common Files\Microsoft Shared\DevServer\10.0\capture.jpg" denied.
    // byte[] captureBytes = ccWebCam.CaptureSTA();

    /// <summary>
    /// Timur Kovalev (http://www.creativecodedesign.com):
    /// This class provides a method of capturing a webcam image via avicap32.dll api.
    /// </summary>    
    public static class ccWebCam
    {
        #region *** PInvoke Stuff - methods to interact with capture window ***

        [DllImport("user32", EntryPoint = "SendMessage")]
        private static extern int SendMessage(int hWnd, uint Msg, int wParam, int lParam);

        [DllImport("avicap32.dll", EntryPoint = "capCreateCaptureWindowA")]
        private static extern int capCreateCaptureWindowA(string lpszWindowName, int dwStyle, 
            int X, int Y, int nWidth, int nHeight, int hwndParent, int nID);  


        private const int WM_CAP_CONNECT = 1034;
        private const int WM_CAP_DISCONNECT = 1035;
        private const int WM_CAP_COPY = 1054;
        private const int WM_CAP_GET_FRAME = 1084;


        #endregion


        private static object objWebCamThreadLock = new object();


        //CaptureToFile(@"D:\Stefan.Steiger\Documents\Visual Studio 2010\Projects\Post_Ipag\image3.jpg"):
        public static bool Capture(string filePath, int connectDelay = 500)
        {
            lock (objWebCamThreadLock)
            {
                return cc.Utility.ccWebCam.InternalCaptureAsFileInThread(filePath, connectDelay);
            }
        } // End Treadsafe Function Capture


        public static byte[] Capture(int connectDelay = 500)
        {
            lock (objWebCamThreadLock)
            {
                return InternalCaptureToByteArrayInThread(connectDelay);
            }
        } // End Treadsafe Function Capture


        /// <summary>
        /// Captures a frame from the webcam and returns the byte array associated
        /// with the captured image. The image is also stored in a file
        /// </summary>
        /// <param name="filePath">path the file wher ethe image will be saved</param>
        /// <param name="connectDelay">number of milliseconds to wait between connect 
        /// and capture - necessary for some cameras that take a while to 'warm up'</param>
        /// <returns>true on success, false on failure</returns>
        private static bool InternalCaptureAsFileInThread(string filePath, int connectDelay = 500)
        {
            bool success = false;
            Thread catureThread = new Thread(() =>
            {
                success = InternalCaptureAsFile(filePath, connectDelay);
            });
            catureThread.SetApartmentState(ApartmentState.STA);
            catureThread.Start();
            catureThread.Join();
            return success;
        } // End Function InternalCaptureAsFileInThread


        /// <summary>
        /// Captures a frame from the webcam and returns the byte array associated
        /// with the captured image. The image is also stored in a file
        /// </summary>
        /// <param name="filePath">path the file wher ethe image will be saved</param>
        /// <param name="connectDelay">number of milliseconds to wait between connect 
        /// and capture - necessary for some cameras that take a while to 'warm up'</param>
        /// <returns>true on success, false on failure</returns>
        private static bool InternalCaptureAsFile(string filePath, int connectDelay = 500)
        {
            byte[] capture = ccWebCam.InternalCaptureToByteArray(connectDelay);
            if (capture != null)
            {
                // Access to path C:\Program Files (x86)\Common Files\Microsoft Shared\DevServer\10.0\image1.jpg" denied.
                File.WriteAllBytes(filePath, capture);
                return true;
            }
            return false;
        } // End Function InternalCaptureAsFile


        /// <summary>
        /// Captures a frame from the webcam and returns the byte array associated
        /// with the captured image. Runs in a newly-created STA thread which is 
        /// required for this method of capture
        /// </summary>
        /// <param name="connectDelay">number of milliseconds to wait between connect 
        /// and capture - necessary for some cameras that take a while to 'warm up'</param>
        /// <returns>byte array representing a bitmp or null (if error or no webcam)</returns>
        private static byte[] InternalCaptureToByteArrayInThread(int connectDelay = 500)
        {
            byte[] bytes = null;
            Thread catureThread = new Thread(() =>
            {
                bytes = InternalCaptureToByteArray(connectDelay);
            });
            catureThread.SetApartmentState(ApartmentState.STA);
            catureThread.Start();
            catureThread.Join();
            return bytes;
        } // End Function InternalCaptureToByteArrayInThread


        /// <summary>
        /// Captures a frame from the webcam and returns the byte array associated
        /// with the captured image
        /// </summary>
        /// <param name="connectDelay">number of milliseconds to wait between connect 
        /// and capture - necessary for some cameras that take a while to 'warm up'</param>
        /// <returns>byte array representing a bitmp or null (if error or no webcam)</returns>
        private static byte[] InternalCaptureToByteArray(int connectDelay = 500)
        {
            Clipboard.Clear();                                              // clear the clipboard
            int hCaptureWnd = capCreateCaptureWindowA("ccWebCam", 0, 0, 0,  // create the hidden capture window
                350, 350, 0, 0);
            SendMessage(hCaptureWnd, WM_CAP_CONNECT, 0, 0);                 // send the connect message to it
            Thread.Sleep(connectDelay);                                     // sleep the specified time
            SendMessage(hCaptureWnd, WM_CAP_GET_FRAME, 0, 0);               // capture the frame
            SendMessage(hCaptureWnd, WM_CAP_COPY, 0, 0);                    // copy it to the clipboard
            SendMessage(hCaptureWnd, WM_CAP_DISCONNECT, 0, 0);              // disconnect from the camera
            Bitmap bitmap = (Bitmap)Clipboard.GetDataObject().GetData(DataFormats.Bitmap);  // copy into bitmap

            if (bitmap == null)
                return null;

            using (MemoryStream stream = new MemoryStream())
            {
                bitmap.Save(stream, ImageFormat.Bmp);    // get bitmap bytes
                return stream.ToArray();
            } // End Using stream

        } // End Function InternalCaptureToByteArray


    }


}

Я пробовал так, но он получает только черное изображение...

    [DllImport("user32.dll")]
    static extern IntPtr GetWindowDC(IntPtr hWnd);

    [DllImport("gdi32.dll", SetLastError = true)]
    static extern IntPtr CreateCompatibleDC(IntPtr hdc);

    enum TernaryRasterOperations : uint
    {
        /// <summary>dest = source</summary>
        SRCCOPY = 0x00CC0020,
        /// <summary>dest = source OR dest</summary>
        SRCPAINT = 0x00EE0086,
        /// <summary>dest = source AND dest</summary>
        SRCAND = 0x008800C6,
        /// <summary>dest = source XOR dest</summary>
        SRCINVERT = 0x00660046,
        /// <summary>dest = source AND (NOT dest)</summary>
        SRCERASE = 0x00440328,
        /// <summary>dest = (NOT source)</summary>
        NOTSRCCOPY = 0x00330008,
        /// <summary>dest = (NOT src) AND (NOT dest)</summary>
        NOTSRCERASE = 0x001100A6,
        /// <summary>dest = (source AND pattern)</summary>
        MERGECOPY = 0x00C000CA,
        /// <summary>dest = (NOT source) OR dest</summary>
        MERGEPAINT = 0x00BB0226,
        /// <summary>dest = pattern</summary>
        PATCOPY = 0x00F00021,
        /// <summary>dest = DPSnoo</summary>
        PATPAINT = 0x00FB0A09,
        /// <summary>dest = pattern XOR dest</summary>
        PATINVERT = 0x005A0049,
        /// <summary>dest = (NOT dest)</summary>
        DSTINVERT = 0x00550009,
        /// <summary>dest = BLACK</summary>
        BLACKNESS = 0x00000042,
        /// <summary>dest = WHITE</summary>
        WHITENESS = 0x00FF0062,
        /// <summary>
        /// Capture window as seen on screen.  This includes layered windows 
        /// such as WPF windows with AllowsTransparency="true"
        /// </summary>
        CAPTUREBLT = 0x40000000
    }

    [DllImport("gdi32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool BitBlt(IntPtr hdc, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, TernaryRasterOperations dwRop);

    [DllImport("gdi32.dll")]
    static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight);

    [DllImport("gdi32.dll", ExactSpelling = true, PreserveSig = true, SetLastError = true)]
    static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);

    [DllImport("gdi32.dll")]
    static extern bool DeleteDC(IntPtr hdc);

    [DllImport("user32.dll")]
    static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDC);

    [DllImport("gdi32.dll")]
    static extern bool DeleteObject(IntPtr hObject);


    public static void ScreenshotWindow(IntPtr windowHandle)
    {
        Rect Rect = new Rect();

        GetWindowRect(windowHandle, ref Rect);
        int width = Rect.Right - Rect.Left;
        int height = Rect.Bottom - Rect.Top;

        IntPtr windowDeviceContext = GetWindowDC(windowHandle);
        IntPtr destDeviceContext = CreateCompatibleDC(windowDeviceContext);
        IntPtr bitmapHandle = CreateCompatibleBitmap(windowDeviceContext, width, height);
        IntPtr oldObject = SelectObject(destDeviceContext, bitmapHandle);

        BitBlt(destDeviceContext, 0, 0, width, height, windowDeviceContext, 0, 0, TernaryRasterOperations.CAPTUREBLT | TernaryRasterOperations.SRCCOPY);
        SelectObject(destDeviceContext, oldObject);

        DeleteDC(destDeviceContext);
        ReleaseDC(windowHandle, destDeviceContext);


        Image screenshot = Image.FromHbitmap(bitmapHandle);
        DeleteObject(bitmapHandle);

        screenshot.Save("d:\\temp\\mywebcamimage.png", System.Drawing.Imaging.ImageFormat.Png);

        /*
        // TODO - Remove above save when it works
        using (MemoryStream stream = new MemoryStream())
        {
            screenshot.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
            return stream.ToArray();
        }
        */
    }

А это после SendMessage(hCaptureWnd, WM_CAP_GET_FRAME, 0, 0);

ScreenshotWindow(new IntPtr(hCaptureWnd));

person Stefan Steiger    schedule 24.04.2013    source источник
comment
Я не понимаю, почему вы просто... не копируете изображение в буфер обмена, а вместо этого конвертируете его непосредственно в массив байтов. Эта логика содержится в обработчике сообщений WM_CAP_COPY? Какая библиотека предоставляет это сообщение? Можете ли вы переопределить его поведение?   -  person Cody Gray    schedule 24.04.2013
comment
@Cody Grey: Это библиотека Windows! WinAPI! И нет, я не могу менять окна, у меня нет исходников (не то чтобы я не мог разобрать и пропатчить, но тут неправильный подход) ;) И на ваш вопрос: Потому что я не умею создать образ из hCaptureWnd, в чем кстати вопрос...   -  person Stefan Steiger    schedule 24.04.2013
comment
Хм, почему я не могу найти на него документацию? Думаю, я не в своей лиге здесь, я никогда не слышал об этом.   -  person Cody Gray    schedule 24.04.2013
comment
@Cody Gray: это собственные функции операционной системы Windows (C++, а не C#). Вы найдете документацию WinAPI на msdn. Мой код вызывает dll C/C++ с именем avicap32.dll, который является частью Windows.   -  person Stefan Steiger    schedule 24.04.2013
comment
VFW (avicap32) не лучший выбор API, API устарел, не удобен, не каждую камеру подхватывает, ограничен в наборе функций.   -  person Roman R.    schedule 24.04.2013
comment
Хорошо, я пытался помочь, но в вашем коде используются выдуманные имена констант сообщений. Так что я слишком запутался, чтобы идти дальше.   -  person David Heffernan    schedule 24.04.2013
comment
@David Heffernan: Да, имена констант немного неправильные, но это не моя работа;)   -  person Stefan Steiger    schedule 24.04.2013
comment
Я собирался сказать то, что сказал Роман, но выплюнул свою болванку из-за сбивающих с толку имен!   -  person David Heffernan    schedule 24.04.2013
comment
Да, я знаю, что такое Windows API. Я просто не узнал это конкретное сообщение. Это имеет смысл теперь, когда я знаю, что вы изменили имя. Я просто недостаточно хорошо знаю этот API, чтобы увидеть это. Как уже говорили другие, это старый, не лучший способ для захвата видео. В будущем я предлагаю сохранить исходные имена для ваших констант. Это значительно облегчает людям понимание вашего кода.   -  person Cody Gray    schedule 25.04.2013


Ответы (3)


Нет такой вещи, как WM_CAP_GET_FRAME. Правильное имя сообщения: WM_CAP_GRAB_FRAME и это описано в MSDN.

Что он делает:

Сообщение WM_CAP_GRAB_FRAME извлекает и отображает один кадр из драйвера захвата. После захвата наложение и предварительный просмотр отключаются. Это сообщение можно отправить явно или с помощью макроса capGrabFrame.

Для получения актуальных данных необходимо использовать обратный вызов кадра, как описано далее в MSDN. Обратный вызов дает вам байты изображения, которые вы можете записать в файл или использовать для любой обработки без необходимости передачи через буфер обмена.

...это функция обратного вызова, используемая при потоковом захвате для дополнительной обработки кадра захваченного видео. Имя capVideoStreamCallback является заполнителем имени функции, предоставленной приложением.

[А у вас там есть]... Указатель на структуру VIDEOHDR, содержащую информацию о захваченном кадре.

Опять же, этот API — неудачный выбор для захвата видео. Слишком старый, слишком ограниченный.

person Roman R.    schedule 24.04.2013

Вы должны отправить другое сообщение, а именно WM_CAP_FILE_SAVEDIB, чтобы сохранить данные в файле на диске. Затем вы сможете загрузить его в объект Bitmap для дальнейшей обработки (я не знаю ни о каких встроенных функциях cam-to-byte[]).

[DllImport("user32", EntryPoint = "SendMessage")]
private static extern int SendMessage(
    int hWnd, uint Msg, int wParam, string strFileName);

private const int WM_USER = 0x0400;
private const int WM_CAP_START = WM_USER;
private const int WM_CAP_FILE_SAVEDIB = WM_CAP_START + 25;

//before
SendMessage(hCaptureWnd, WM_CAP_COPY, 0, 0);

//after
string tempFile = Server.MapPath("~/App_Data/tempCap.bmp");
SendMessage(hCaptureWnd, WM_CAP_FILE_SAVEDIB, 0, tempFile); //create tempfile
Bitmap bitmap = new Bitmap(tempFile); //read tempfile
using (MemoryStream stream = new MemoryStream())
{
    bitmap.Save(stream, ImageFormat.Bmp);
    return stream.ToArray();
}
person Alex    schedule 24.04.2013
comment
Хорошо, это для файла, а что для байтового массива (без загрузки файла)? - person Stefan Steiger; 24.04.2013
comment
Я не думаю, что dll предоставляет прямое сообщение cam-to-byte[] - person Alex; 24.04.2013
comment
Ну, конечно, нет, но мы надеемся, что WinAPI предоставляет способ преобразования hCaptureWnd в растровое изображение. - person Stefan Steiger; 24.04.2013
comment
Вот для чего нужен WM_CAP_FILE_SAVEDIB. Я завершил фрагмент кода, добавив строки для чтения временного файла обратно в код для создания byte[] - person Alex; 24.04.2013
comment
Да, именно так не надо делать в веб-приложении, потому что для этого нужен доступ на запись для веб-сервера в этот каталог. Я бы конечно прописал его в директорию в корневом каталоге приложения, но все равно плохо. PS: я изменил ваш код, включив в него константу и дополнительную перегрузку sendmessage. Это работает, но это не то, что я хочу :) - person Stefan Steiger; 24.04.2013
comment
Раньше не было упоминаний о веб-приложениях, или я пропустил это. В любом случае, App_Data предназначен для поддержки файлового хранилища и может использоваться для безопасного хранения вещей (все, что там находится, недоступно из Интернета по умолчанию). Другим жизнеспособным путем будет расположение ваших журналов. - person Alex; 24.04.2013
comment
Я не упомянул об этом напрямую, но я написал цитату, что Visual Studio требует времени, чтобы начать отладку веб-приложения :) За исключением того, что приложение не знает, где находится путь к файлу журнала (я не хочу начинать настраивать это, потому что это наиболее конечно, разные для каждой версии и для пользовательской конфигурации), плюс IIS пишет в журнал событий, а не в файл журнала. - person Stefan Steiger; 24.04.2013

Опираясь на ответ Романа Р.:

Более тонкий момент морали заключается в том, что вам нужно зарегистрировать кадр обратного вызова, а затем вызвать захват, и что вы не можете напрямую преобразовать char[] в стиле C в byte[], и что вы получаете необработанные данные растрового изображения - не растровое изображение, а что размер изображения составляет 640x480, независимо от того, что установлено в capCreateCaptureWindowA, и что lpData должен быть IntPtr, а не UIntPtr, потому что Marshal.Copy не имеет перегрузки для UIntPtr, и что с помощью WriteBitmapFile можно записывать необработанные растровые данные в растровое изображение БЕЗ использования небезопасного кода или сопоставления заголовков файла растрового изображения, и тот, кто написал Marshal.Copy, сделал возможным копирование отрицательного значения, потому что длина - это int, а не uint...

Кроме того, по какой-то причине необходимо повернуть изображение на 180 градусов...
Также я изменил константы WM на их правильные имена.

    SendMessage(hCaptureWnd, WM_CAP_SET_CALLBACK_FRAME, 0, capVideoStreamCallback);
    SendMessage(hCaptureWnd, WM_CAP_GRAB_FRAME, 0, 0);               // capture the frame

С этими дополнительными вещами

    // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757688(v=vs.85).aspx
    [StructLayout(LayoutKind.Sequential)]
    private struct VIDEOHDR 
    {
        // http://msdn.microsoft.com/en-us/library/windows/desktop/aa383751(v=vs.85).aspx


        // typedef unsigned char BYTE;
        // typedef BYTE far *LPBYTE;
        // unsigned char* lpData


        //public byte[] lpData; // LPBYTE    lpData; // Aaargh, invalid cast, not a .NET byte array...
        public IntPtr lpData; // LPBYTE    lpData;
        public UInt32 dwBufferLength; // DWORD     dwBufferLength;
        public UInt32 dwBytesUsed; // DWORD     dwBytesUsed;
        public UInt32 dwTimeCaptured; // DWORD     dwTimeCaptured;


        // typedef ULONG_PTR DWORD_PTR;
        // #if defined(_WIN64)  
        //   typedef unsigned __int64 ULONG_PTR;
        // #else
        //   typedef unsigned long ULONG_PTR;
        // #endif
        public IntPtr dwUser; // DWORD_PTR dwUser; 
        public UInt32 dwFlags; // DWORD     dwFlags;

        [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst = 4)]
        public System.UIntPtr[] dwReserved; // DWORD_PTR dwReserved[4];

        // Does not make a difference
        //public System.UIntPtr[] dwReserved = new System.UIntPtr[4]; // DWORD_PTR dwReserved[4];
    }




    private delegate System.IntPtr capVideoStreamCallback_t(System.UIntPtr hWnd, ref VIDEOHDR lpVHdr);
    [DllImport("user32", EntryPoint = "SendMessage")]
    private static extern int SendMessage(int hWnd, uint Msg, int wParam, capVideoStreamCallback_t routine);




    // http://eris.liralab.it/yarpdoc/vfw__extra__from__wine_8h.html
    private const int WM_USER = 0x0400; // 1024
    private const int WM_CAP_START = WM_USER;
    private const int WM_CAP_DRIVER_CONNECT = WM_CAP_START + 10;
    private const int WM_CAP_DRIVER_DISCONNECT = WM_CAP_START + 11;

    private const int WM_CAP_FILE_SAVEDIB = WM_CAP_START + 25;
    private const int WM_CAP_SET_CALLBACK_FRAME = WM_CAP_START + 5;
    private const int WM_CAP_GRAB_FRAME = WM_CAP_START + 60;
    private const int WM_CAP_EDIT_COPY = WM_CAP_START + 30;




    // http://lists.ximian.com/pipermail/mono-devel-list/2011-March/037272.html

    private static byte[] baSplendidIsolation;


    private static System.IntPtr capVideoStreamCallback(System.UIntPtr hWnd, ref VIDEOHDR lpVHdr)
    {
        //System.Windows.Forms.MessageBox.Show("hello");
        //System.Windows.Forms.MessageBox.Show(lpVHdr.dwBufferLength.ToString() + " " + lpVHdr.dwBytesUsed.ToString());
        byte[] _imageTemp = new byte[lpVHdr.dwBufferLength];
        Marshal.Copy(lpVHdr.lpData, _imageTemp, 0, (int) lpVHdr.dwBufferLength);
        //System.IO.File.WriteAllBytes(@"d:\temp\mycbfile.bmp", _imageTemp); // AAaaarg, it's raw bitmap data...

        // http://stackoverflow.com/questions/742236/how-to-create-a-bmp-file-from-byte-in-c-sharp
        // http://stackoverflow.com/questions/2654480/writing-bmp-image-in-pure-c-c-without-other-libraries

        // Tsssss... 350 x 350 was the expected setting, but never mind... 
        // fortunately alex told me about WM_CAP_FILE_SAVEDIB, so I could compare to the direct output
        int width = 640;
        int height = 480;
        int stride = width*3;

        baSplendidIsolation = null;
        baSplendidIsolation = WriteBitmapFile(@"d:\temp\mycbfilecc.bmp", width, height, _imageTemp);

        /*
        unsafe
        {
            fixed (byte* ptr = _imageTemp)
            {
                using (Bitmap image = new Bitmap(width, height, stride, PixelFormat.Format24bppRgb, new IntPtr(ptr)))
                {
                    image.Save(@"d:\temp\mycbfile2.bmp");
                }
            }
        }
        */

        //var hdr = (Elf32_Phdr)Marshal.PtrToStructure(ptr, typeof(Elf32_Phdr));
        return System.IntPtr.Zero;
    }


    private static byte[] WriteBitmapFile(string filename, int width, int height, byte[] imageData)
    {
        using (var stream = new MemoryStream(imageData))
        using (var bmp = new Bitmap(width, height, PixelFormat.Format24bppRgb))
        {
            BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0,bmp.Width, bmp.Height)
                                                ,ImageLockMode.WriteOnly
                                                ,bmp.PixelFormat
            );

            Marshal.Copy(imageData, 0, bmpData.Scan0, imageData.Length);

            bmp.UnlockBits(bmpData);


            if (bmp == null)
                return null;

            bmp.RotateFlip(RotateFlipType.Rotate180FlipNone);
            bmp.Save(filename); // For testing only

            using (MemoryStream ms = new MemoryStream())
            {
                bmp.Save(ms, ImageFormat.Png);    // get bitmap bytes
                return ms.ToArray();
            } // End Using stream

        }

    } // End Function WriteBitmapFile


    /// <summary>
    /// Captures a frame from the webcam and returns the byte array associated
    /// with the captured image
    /// </summary>
    /// <param name="connectDelay">number of milliseconds to wait between connect 
    /// and capture - necessary for some cameras that take a while to 'warm up'</param>
    /// <returns>byte array representing a bitmp or null (if error or no webcam)</returns>
    private static byte[] InternalCaptureToByteArray(int connectDelay = 500)
    {
        Clipboard.Clear(); 
        int hCaptureWnd = capCreateCaptureWindowA("ccWebCam", 0, 0, 0,
            350, 350, 0, 0); // create the hidden capture window
        SendMessage(hCaptureWnd, WM_CAP_DRIVER_CONNECT, 0, 0); // send the connect message to it
        //SendMessage(hCaptureWnd, WM_CAP_DRIVER_CONNECT, i, 0); // i device number retval != 0 --> valid device_id

        Thread.Sleep(connectDelay);                                     // sleep the specified time
        SendMessage(hCaptureWnd, WM_CAP_SET_CALLBACK_FRAME, 0, capVideoStreamCallback);
        SendMessage(hCaptureWnd, WM_CAP_GRAB_FRAME, 0, 0);               // capture the frame

        //SendMessage(hCaptureWnd, WM_CAP_FILE_SAVEDIB, 0, "d:\\temp\\testmywebcamimage.bmp");
        //ScreenshotWindow(new IntPtr(hCaptureWnd));

        //SendMessage(hCaptureWnd, WM_CAP_EDIT_COPY, 0, 0); // copy it to the clipboard


        // using (Graphics g2 = Graphics.FromHwnd(new IntPtr(hCaptureWnd)))

        SendMessage(hCaptureWnd, WM_CAP_DRIVER_DISCONNECT, 0, 0);              // disconnect from the camera

        return baSplendidIsolation;

        /*
        Bitmap bitmap = (Bitmap)Clipboard.GetDataObject().GetData(DataFormats.Bitmap);  // copy into bitmap

        if (bitmap == null)
            return null;

        using (MemoryStream stream = new MemoryStream())
        {
            bitmap.Save(stream, ImageFormat.Bmp);    // get bitmap bytes
            return stream.ToArray();
        } // End Using stream
        */
    } // End Function InternalCaptureToByteArray
person Stefan Steiger    schedule 25.04.2013
comment
Какое шестнадцатеричное значение WM_CAP_GRAB_FRAME и WM_CAP_SET_CALLBACK_FRAME вы использовали в своем коде. я использую другую, как показано ниже, общедоступную константу WM_CAP_DRIVER_CONNECT = 0x40a; общедоступная константа WM_CAP_DRIVER_DISCONNECT = 0x40b; общедоступная константа WM_CAP_EDIT_COPY = 0x41e; общедоступная константа WM_CAP_SET_PREVIEW = 0x432; общедоступная константа WM_CAP_SET_OVERLAY = 0x433; общедоступная константа WM_CAP_SET_PREVIEWRATE = 0x434; - person Zain Ul Abidin; 07.12.2017