如何复制图像而不使用剪贴板

本文关键字:剪贴板 图像 何复制 复制 | 更新日期: 2023-09-27 18:10:49

问题:我有下面的代码从网络摄像头捕获图像。

我的问题是这部分:

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

它的作用是将图像从窗口复制到剪贴板,然后从中创建一个字节数组。

它工作-只要你不使用剪贴板,而程序正在运行。
问题是,这甚至不适合我自己,因为我有时会复制一些东西,而Visual Studio需要很长时间才能开始调试web应用程序,然后它崩溃了。

我的问题是:
我如何在不使用剪贴板的情况下获得图像?或者更具体地说,如何将hcapturewind转换为System.Drawing.Image ?


——编辑:
我错过了说"不创建文件,我想要一个字节数组"。
这是一个web应用程序,所以运行该应用程序的用户不应该有对文件系统的写访问权限(写文件只是为了临时测试)…
——结束编辑:


/// <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));

如何复制图像而不使用剪贴板

根本没有WM_CAP_GET_FRAME这种东西。消息的正确名称是WM_CAP_GRAB_FRAME,在MSDN上有描述。

的作用是:

WM_CAP_GRAB_FRAME消息检索并显示单个帧从捕获驱动程序。捕获后,叠加和预览禁用。方法发送此消息capGrabFrame宏。

要获得实际数据,您需要使用MSDN上进一步描述的帧回调。回调得到图片字节,您可以将其写入文件或用于任何处理,而无需通过剪贴板传输。

…回调函数与流捕获可选地使用处理一帧捕获的视频。capVideoStreamCallback的名字是应用程序提供的函数名的占位符。

[你有一个]…指向包含捕获帧信息的VIDEOHDR结构的指针。

同样,这个API对于视频捕获来说是不吉利的选择。太老了,太局限了

您必须发送不同的消息,特别是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();
}

建筑在Roman R上。的回答是:

道德的细微之处在于你需要注册回调帧,然后调用grabframe,你不能直接转换c风格的char[]到byte[],你得到原始的位图数据-不是位图,图像大小是640x480,不管在capCreateCaptureWindowA中设置了什么,lpData需要是IntPtr,而不是UIntPtr,因为Marshal。Copy对于UIntPtr没有重载,并且使用WriteBitmapFile,可以将原始位图数据写入位图而不使用不安全代码或映射位图文件头,并且无论谁写Marshal。Copy使得复制负值成为可能,因为length是int,而不是int…

此外,无论出于何种原因,都需要将图像旋转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