How to copy an image without using the clipboard?

Question: I have the code below for capturing images from a webcam.

My problem in this part:

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

What he does is copy the image from the window to the clipboard, and then create an array of bytes from it.

It works - until you use the clipboard while the program is running.
The problem is that this does not even work for me, as I sometimes copy something, and Visual Studio takes a long time to start debugging the web application and then crash.

So here is my question:
How can I get an image without using the clipboard? Or more specifically, how to convert hCaptureWnd to System.Drawing.Image?


- Change:
I skipped to say "without creating a file, I want an array of bytes."
This is a web application, so the user who runs the application should not have write access to the file system (write to the file only for temporary testing) ...
- 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 

Note ( 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; 

Full code for reference

 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 } } 

I tried like this, but it only gets a black image ...

  [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(); } */ } 

And then this is after SendMessage(hCaptureWnd, WM_CAP_GET_FRAME, 0, 0);

 ScreenshotWindow(new IntPtr(hCaptureWnd)); 
+6
source share
3 answers

Based on the novel R. Answer:

A finer moral is that you need to register a callback frame and then call grabframe, and that you cannot directly use C-style char [] for byte [], and you get raw bitmap data - not a bitmap, but size images are 640x480, regardless of what is set in capCreateCaptureWindowA, and that lpData must be IntPtr, not UIntPtr, because Marshal.Copy does not have overload for UIntPtr, and that using WriteBitmapFile you can write raw bitmap data to a bitmap WITHOUT use unsafe wow code or mapping the headers of raster image files, and the one who wrote Marshal.Copy allowed to copy a negative value, because the length is int, not uint ...

In addition, there is a need to rotate the image 180 oldDegrees for any reason ...
In addition, I changed the WM constants to their correct names.

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

With these extra things

  // 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 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-cc-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 
+1
source

There is no such thing as WM_CAP_GET_FRAME . The correct message name is WM_CAP_GRAB_FRAME and is described on MSDN.

What does he do:

The WM_CAP_GRAB_FRAME message retrieves and displays one frame from the capture driver. After capture, overlay and preview is disabled. You can send this message explicitly or using the capGrabFrame macro.

To get the actual data, you need to use a callback as described later on MSDN . The callback gives you image bytes, which you can write to a file or use for any processing without having to send it through the clipboard.

... is a callback function used to process the frame of the captured video. The name capVideoStreamCallback is a placeholder for the name of the function specified in the application.

[And you have] ... Pointer to a VIDEOHDR structure containing information about the captured frame.

Again, this API is a bad choice for capturing video. Too old, too limited.

+6
source

You need to send another message, in particular WM_CAP_FILE_SAVEDIB , to save the data in a file on disk. Then you can load it into a Bitmap object for further processing (I do not know about the built-in function "one by one").

 [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(); } 
+3
source

Source: https://habr.com/ru/post/943507/


All Articles