Monthly Archives: October 2010

Capturing a still image from a Webcam in .net

Standard

image 

What if you want to capture a single image from a webcam?  It turns out that this is quite straight forward to do using a couple of Win32 api calls to the avicap32.dll library.

I encapsulated these api calls in a simple class.

To write a file called “snapshot.jpg” out to the desktop from a single webcam, simply declare the class and call a the TakePicture method.

Dim loSnap As New WebcamSnap
loSnap.TakePicture(0)

The devices collection shows a list of all webcam devices on your machine so you can populate a combo with them.

Note: This example has no decent error handling, which I suspect will be needed in the “real world” of multiple devices.

Here’s the WebcamSnap class:

Option Explicit On
Imports System.IO
Imports System.Runtime.InteropServices
''' <summary>
''' This class takes a snapshot from any or all attached webcams
''' </summary>
''' <remarks></remarks>
Public Class WebcamSnap
    Const WM_CAP As Short = &H400S

    Const WM_CAP_DRIVER_CONNECT As Integer = WM_CAP + 10
    Const WM_CAP_DRIVER_DISCONNECT As Integer = WM_CAP + 11
    Const WM_CAP_EDIT_COPY As Integer = WM_CAP + 30
    Const WS_CHILD As Integer = &H40000000
    Const WS_VISIBLE As Integer = &H10000000
    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 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 Short, _
        ByVal lpszName As String, ByVal cbName As Integer, ByVal lpszVer As String, _
        ByVal cbVer As Integer) As Boolean

    Public Devices As New List(Of String)
    Public Height As Integer = 480
    Public Width As Integer = 640
    Public OutputPath As String = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory)
    Public FilenamePrefix As String = "snapshot"
    Public Sub New()
        mLoadDeviceList()
    End Sub
    Private Sub mLoadDeviceList()
        Dim lsName As String = Space(100)
        Dim lsVers As String = Space(100)
        Dim lbReturn As Boolean
        Dim x As Integer = 0

        Do
            '   Get Driver name and version
            lbReturn = capGetDriverDescriptionA(x, lsName, 100, lsVers, 100)

            ' If there was a device add device name to the list
            If lbReturn Then Devices.Add(lsName.Trim)
            x += 1
        Loop Until lbReturn = False
    End Sub
    Public Sub TakePicture()
        For i = 0 To Me.Devices.Count - 1
            Dim lsFilename As String = Path.Combine(OutputPath, Me.FilenamePrefix & i & ".jpg")
            TakePicture(i, lsFilename)
        Next
    End Sub
    Public Sub TakePicture(ByVal iDevice As Integer)
        Me.TakePicture(iDevice, Path.Combine(OutputPath, Me.FilenamePrefix & ".jpg"))
    End Sub
    Public Sub TakePicture(ByVal iDevice As Integer, ByVal filename As String)

        Dim lhHwnd As Integer ' Handle to preview window

        ' Create a form to play with
        Using loWindow As New System.Windows.Forms.Form

            ' Create capture window
            lhHwnd = capCreateCaptureWindowA(iDevice, WS_VISIBLE Or WS_CHILD, 0, 0, Me.Width, _
               Me.Height, loWindow.Handle.ToInt32, 0)

            ' Hook up the device
            SendMessage(lhHwnd, WM_CAP_DRIVER_CONNECT, iDevice, 0)
            ' Allow the webcam apeture to let enough light in
            For i = 1 To 10
                Application.DoEvents()
            Next

            ' Copy image to clipboard
            SendMessage(lhHwnd, WM_CAP_EDIT_COPY, 0, 0)

            ' Get image from clipboard and convert it to a bitmap
            Dim loData As IDataObject = Clipboard.GetDataObject()
            If loData.GetDataPresent(GetType(System.Drawing.Bitmap)) Then
                Using loBitmap As Image = CType(loData.GetData(GetType(System.Drawing.Bitmap)), Image)
                    loBitmap.Save(filename, Imaging.ImageFormat.Jpeg)
                End Using
            End If

            SendMessage(lhHwnd, WM_CAP_DRIVER_DISCONNECT, iDevice, 0)

        End Using

    End Sub

End Class

Links

I found the original code sample here:

http://www.experts-exchange.com/Programming/Languages/.NET/Visual_Basic.NET/Q_22482234.html

(Although I’ve seen it all over the web in various guises)

In hell with SSL and WCF

Standard

 image

A long time ago I decided that WCF was one of those things I wouldn’t try and learn about in preference to learning other things, such as Silverlight, MVC and how to cook cookies properly.  Now that decision has come back to haunt me.

It seemed simple.  A client server prototype using wsHttpBindings and WCF.  My initial Project’s configuration files worked well on my local development machine (http).

The time came to put this on the server using SSL (https).

Much of the documentation on the web says that the simplest way of doing this is to configure IIS (IIS6 in my case) to work with SSL, then copy it over.  Words cannot express how wrong this proved to be.

For future reference, rest assured you will need very different versions of these files for local development as opposed to a production server.

I’ve also learnt that WCF gives as good as it gets in the ongoing competition between software development technologies in providing weird and useless error messages that are no hope at all in solving problems.  (Although handy for doing google searches to find others as miserable as you are)

Before

image

My original (Visual Studio generated) client app.config file looked like this:

<system.serviceModel>
  <bindings>
    <wsHttpBinding>
      <binding name="WSHttpBinding_IClientConnect" closeTimeout="00:01:00"
          openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
          bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard"
          maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
          messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true"
          allowCookies="false">
        <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
            maxBytesPerRead="4096" maxNameTableCharCount="16384" />
        <reliableSession ordered="true" inactivityTimeout="00:10:00"
            enabled="false" />
        <security mode="Message">
          <transport clientCredentialType="Windows" proxyCredentialType="None"
              realm="" />
          <message clientCredentialType="Windows" negotiateServiceCredential="true"
              algorithmSuite="Default" />
        </security>
      </binding>
    </wsHttpBinding>
  </bindings>
  <client>
    <endpoint address="http://localhost:10916/SecureClientDemoServer/ClientConnect.svc"
        binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IClientConnect"
        contract="ClientService.IClientConnect" name="WSHttpBinding_IClientConnect">
      <identity>
        <dns value="localhost" />
      </identity>
    </endpoint>
  </client>
</system.serviceModel>

My server web.config file looked like this:

    <system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
        <services>
   <service behaviorConfiguration="ServiceBehavior" name="ClientConnect">
    <endpoint address="" binding="wsHttpBinding" contract="IClientConnect">
     <identity>
      <dns value="localhost" />
     </identity>
    </endpoint>
    <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
   </service>
  </services>
        <behaviors>
            <serviceBehaviors>
                <behavior name="ServiceBehavior">
                    <!– To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment –>
                    <serviceMetadata httpGetEnabled="true"/>
                    <!– To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information –>
                    <serviceDebug includeExceptionDetailInFaults="false"/>
                </behavior>
            </serviceBehaviors>
        </behaviors>
    </system.serviceModel>

 

After

 image

app.config

<system.serviceModel>
   <bindings>
    <wsHttpBinding>
      <binding name="WSHttpBinding_IClientConnect" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard" maxBufferPoolSize="524288" maxReceivedMessageSize="65536" messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true" allowCookies="false">
        <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384"/>
        <reliableSession ordered="true" inactivityTimeout="00:10:00" enabled="false"/>
        <security mode="Transport">
          <transport clientCredentialType="None" proxyCredentialType="None" realm=""/>
          <message clientCredentialType="None"  algorithmSuite="Default" negotiateServiceCredential="false" establishSecurityContext="false" /> 
        </security>
      </binding>
    </wsHttpBinding>
  </bindings>
  <client>
    <endpoint address=https://remoteserver/SecureClientDemo/ClientConnect.svc binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IClientConnect" contract="ClientService.IClientConnect" name="WSHttpBinding_IClientConnect">
    </endpoint>
  </client>
</system.serviceModel>

 

web.config

    <system.serviceModel>
        <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
        <services>
            <service behaviorConfiguration="ServiceBehavior" name="ClientConnect">
                     <endpoint address=https://remoteserver/SecureClientDemo/ClientConnect.svc bindingConfiguration="HttpsBinding" binding="wsHttpBinding" contract="IClientConnect">
                </endpoint>
                <endpoint address="mex" binding="mexHttpsBinding" contract="IMetadataExchange"
                                 name="MexHttpsBindingEndpoint" 
                                 />
            </service>
        </services>
        <behaviors>
            <serviceBehaviors>
                <behavior name="ServiceBehavior">
                    <!– To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment –>
                    <serviceMetadata httpGetEnabled="false" httpsGetEnabled="true"/>
                    <!– To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information –>
                    <serviceDebug includeExceptionDetailInFaults="false"/>
                </behavior>
            </serviceBehaviors>
        </behaviors>
           <bindings>
         <wsHttpBinding>
          <binding name="HttpsBinding">
            <security mode="Transport">
              <transport clientCredentialType="None"/>
            </security>
          </binding>
        </wsHttpBinding>
            </bindings>
    </system.serviceModel>

 

 

Conclusion

This article is intended to show on the web a working set of configuration parameters, so for the time being I don’t have time to go into and explanation I only half understand.  I hope people find this helpful.

 

Links

http://msdn.microsoft.com/en-us/library/ms729700.aspx

http://stackoverflow.com/questions/1521117/wcf-over-ssl-404-error

http://stackoverflow.com/questions/2435823/the-provided-uri-scheme-https-is-invalid-expected-http-parameter-name-via

http://msdn.microsoft.com/en-us/library/ff648840.aspx