Question: I have the below code to capture an image from a webcam.
My problem is this part:
SendMessage(hCaptureWnd, WM_CAP_COPY, 0, 0); // copy it to the clipboard
What it does is copy the image from the window to the clipboard, and then create a byte array out of it.
It works - as long as you don't use the clipboard while the program is running.
The problem is, this doesn't even work for myself, as I sometimes copy something while Visual Studio takes ages to start debug the web application, and then it crashes.
So here my question:
How can I get the image without using the clipboard ?
Or more specifically, how to transform hCaptureWnd to System.Drawing.Image ?
-- Edit:
I missed to say "without creating a file, i want a byte array".
It's a web application, so the user the application runs under shouldn't have write access to the file system (writing to a 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 after SendMessage(hCaptureWnd, WM_CAP_GET_FRAME, 0, 0);
ScreenshotWindow(new IntPtr(hCaptureWnd));