You might have a scenario where you need to use the webcam to take a picture from your application. I am not talking here about pirating a remote webcam. I am talking about your local webcam sitting on the top of your screen or its bezel.
The downloadable demo
This month’s downloadable demo solution contains both VB and C# projects. The solution was created using Visual Studio 2017 but should also work in most older versions as well.
This code will work from a Windows Forms application or from a WPF application. Don’t even think about using it from a web application.
No additional references are required as it is using built-in Windows features.
Building the form
A simple form with a few controls just to enable us to take a few pictures from your webcam.
Figure 1: The demo application in action
Namely we have:
No special properties have been set other then their name, caption. The PictureBox is also anchored.
Writing the code
You will want to download the demo application if you want to see the fully working code. I will only paste the important code here.
First, we need to declare some constants and some commands that we will use from the Windows OS that talks to the webcam itself.
Private Const WM_CAP As Short = &H400S Private Const WM_CAP_DRIVER_CONNECT As Integer = WM_CAP + 10 Private Const WM_CAP_DRIVER_DISCONNECT As Integer = WM_CAP + 11 Private Const WM_CAP_EDIT_COPY As Integer = WM_CAP + 30 Private Const WM_CAP_SET_PREVIEW As Integer = WM_CAP + 50 Private Const WM_CAP_SET_PREVIEWRATE As Integer = WM_CAP + 52 Private Const WM_CAP_SET_SCALE As Integer = WM_CAP + 53 Private Const WS_CHILD As Integer = &H40000000 Private Const WS_VISIBLE As Integer = &H10000000 Private Const SWP_NOMOVE As Short = &H2S Private Const SWP_NOZORDER As Short = &H4S Private Const HWND_BOTTOM As Short = 1 Declare Function SendMessage Lib "user32" Alias "SendMessageA" ( ByVal hwnd As Integer, ByVal wMsg As Integer, ByVal wParam As Integer, <MarshalAs(UnmanagedType.AsAny)> ByVal lParam As Object) As Integer Declare Function SetWindowPos Lib "user32" Alias "SetWindowPos" ( ByVal hwnd As Integer, ByVal hWndInsertAfter As Integer, ByVal x As Integer, ByVal y As Integer, ByVal cx As Integer, ByVal cy As Integer, ByVal wFlags As Integer) As Integer Declare Function DestroyWindow Lib "user32" (ByVal hndw As Integer) As Boolean Declare Function capCreateCaptureWindowA Lib "avicap32.dll" ( ByVal lpszWindowName As String, ByVal dwStyle As Integer, ByVal x As Integer, ByVal y As Integer, ByVal nWidth As Integer, ByVal nHeight As Short, ByVal hWndParent As Integer, ByVal nID As Integer) As Integer Declare Function capGetDriverDescriptionA Lib "avicap32.dll" ( ByVal wDriver As Integer, ByVal lpszName As String, ByVal cbName As Integer, ByVal lpszVer As String, ByVal cbVer As Integer) As Boolean
private const short WM_CAP = 0x400; private const int WM_CAP_DRIVER_CONNECT = WM_CAP + 10; private const int WM_CAP_DRIVER_DISCONNECT = WM_CAP + 11; private const int WM_CAP_EDIT_COPY = WM_CAP + 30; private const int WM_CAP_SET_PREVIEW = WM_CAP + 50; private const int WM_CAP_SET_PREVIEWRATE = WM_CAP + 52; private const int WM_CAP_SET_SCALE = WM_CAP + 53; private const int WS_CHILD = 0x40000000; private const int WS_VISIBLE = 0x10000000; private const short SWP_NOMOVE = 0x2; private const short SWP_NOZORDER = 0x4; private const short HWND_BOTTOM = 1; [DllImport("user32")] static extern int SendMessage( int hwnd, int wMsg, int wParam, [MarshalAs(UnmanagedType.AsAny)] object lParam); [DllImport("user32")] static extern int SetWindowPos( int hwnd, int hWndInsertAfter, int x, int y, int cx, int cy, int wFlags); [DllImport("user32")] static extern bool DestroyWindow(int hndw); [DllImport("avicap32.dll")] static extern int capCreateCaptureWindowA( string lpszWindowName, int dwStyle, int x, int y, int nWidth, short nHeight, int hWndParent, int nID); [DllImport("avicap32.dll")] static extern bool capGetDriverDescriptionA( short wDriverIndex, [MarshalAs(UnmanagedType.VBByRefStr)]ref String lpszName, int cbName, [MarshalAs(UnmanagedType.VBByRefStr)] ref String lpszVer, int cbVer);
Next, we need to load the available devices (webcam) into the listbox control. Normally there will only be one but you might have more (or less) depending on your setup. This method is usually call when the form is initially loaded. If you don’t have anything loaded in the listbox, start by checking if you have a webcam that is connected!
Private Sub LoadDeviceList() Dim strName As String = Space(100) Dim strVer As String = Space(100) Dim bReturn As Boolean Dim x As Integer = 0 Do bReturn = capGetDriverDescriptionA(x, strName, 100, strVer, 100) If bReturn Then lstDevices.Items.Add(strName.Trim) btnStart.Enabled = True End If x += 1 Loop While bReturn End Sub
private void LoadDeviceList() { string strName = "".PadLeft(100); string strVer = "".PadLeft(100); bool bReturn; short x = 0; do { bReturn = capGetDriverDescriptionA(x, ref strName, 100, ref strVer, 100); if (bReturn) { lstDevices.Items.Add(strName.Trim()); btnStart.Enabled = true; } x += 1; } while (bReturn ); }
The next important method is OpenPreviewWindow. This method is called when you click the Start button. It calls the capCreateCaptureWindowA function from Windows and sends the WM_CAP_DRIVER_CONNECT message to start the capture from your webcam. The PictureBox will display the live feed from your webcam.
Private Sub OpenPreviewWindow() _hHwnd = capCreateCaptureWindowA(_iDevice.ToString(), WS_VISIBLE Or WS_CHILD, 0, 0, 640, 480, picCapture.Handle.ToInt32, 0) If SendMessage(_hHwnd, WM_CAP_DRIVER_CONNECT, _iDevice, 0) <> 0 Then SendMessage(_hHwnd, WM_CAP_SET_SCALE, 1, 0) SendMessage(_hHwnd, WM_CAP_SET_PREVIEWRATE, 66, 0) SendMessage(_hHwnd, WM_CAP_SET_PREVIEW, 1, 0) SetWindowPos(_hHwnd, HWND_BOTTOM, 0, 0, picCapture.Width, picCapture.Height, SWP_NOMOVE Or SWP_NOZORDER) btnSave.Enabled = True btnStop.Enabled = True btnStart.Enabled = False Else DestroyWindow(_hHwnd) btnSave.Enabled = False End If End Sub
private void OpenPreviewWindow() { _hHwnd = capCreateCaptureWindowA(_iDevice.ToString(), WS_VISIBLE | WS_CHILD, 0, 0, 640, 480, picCapture.Handle.ToInt32(), 0); if (SendMessage(_hHwnd, WM_CAP_DRIVER_CONNECT, _iDevice, 0) != 0) { SendMessage(_hHwnd, WM_CAP_SET_SCALE, 1, 0); SendMessage(_hHwnd, WM_CAP_SET_PREVIEWRATE, 66, 0); SendMessage(_hHwnd, WM_CAP_SET_PREVIEW, 1, 0); SetWindowPos(_hHwnd, HWND_BOTTOM, 0, 0, picCapture.Width, picCapture.Height, SWP_NOMOVE | SWP_NOZORDER); btnSave.Enabled = true; btnStop.Enabled = true; btnStart.Enabled = false; } else { DestroyWindow(_hHwnd); btnSave.Enabled = false; } }
You can also stop the capture by sending the WM_CAP_DRIVER_DISCONNECT message as shown in the ClosePreviewWindow method. This method is called when you click the Stop button or after an image was taken when clicking the Save button.
Private Sub ClosePreviewWindow() SendMessage(_hHwnd, WM_CAP_DRIVER_DISCONNECT, _iDevice, 0) DestroyWindow(_hHwnd) End Sub
private void ClosePreviewWindow() { SendMessage(_hHwnd, WM_CAP_DRIVER_DISCONNECT, _iDevice, 0); DestroyWindow(_hHwnd); }
This is basically it. It is very basic code providing very basic functionality but it works.
Where is my image saved?
If you look at the code of the Save button, you will find this:
Private Sub btnSave_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnSave.Click SendMessage(_hHwnd, WM_CAP_EDIT_COPY, 0, 0) Dim data As IDataObject = Clipboard.GetDataObject() If data IsNot Nothing AndAlso data.GetDataPresent(GetType(Bitmap)) Then Dim bmap As Image = CType(data.GetData(GetType(Bitmap)), Image) picCapture.Image = bmap picCapture.SizeMode = PictureBoxSizeMode.StretchImage ClosePreviewWindow() btnSave.Enabled = False btnStop.Enabled = False btnStart.Enabled = True Dim strFilename As String = IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), DateTime.Now.ToString("yyyyMMdd_HHmmss") + ".bmp") bmap.Save(strFilename, Imaging.ImageFormat.Bmp) End If End Sub
private void btnSave_Click(object sender, EventArgs e) { SendMessage(_hHwnd, WM_CAP_EDIT_COPY, 0, 0); IDataObject data = Clipboard.GetDataObject(); if (data != null && data.GetDataPresent(typeof(Bitmap))) { Image bmap = (Image)data.GetData(typeof(Bitmap)); picCapture.Image = bmap; picCapture.SizeMode = PictureBoxSizeMode.StretchImage; ClosePreviewWindow(); btnSave.Enabled = false; btnStop.Enabled = false; btnStart.Enabled = true; string strFilename = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), DateTime.Now.ToString("yyyyMMdd_HHmmss") + ".bmp"); bmap.Save(strFilename, ImageFormat.Bmp); } }
This code sends another message to copy the current image to the Clipboard. Then the Clipboard is queried, copied to the PictureBox and also saved under the “Pictures” special folder (same place where you find Documents, Downloads, Music).
Conclusion
Interesting feature to add to your application for about 100 lines of code if you need it. And because this using the Windows API, there is no costly (but often essential) third-party to use.