Introduction
Silverlight brings a whole new set of controls and libraries for developing rich internet applications that use media content like video and audio. In this article
I explore Silverlight's capability for webcam access and the subsequent use of the webcam feed to freeze a frame and save it as a PNG file onto disk.
The code presented here can be wrapped into a nice user control and used in any Silverlight application to capture images, e.g., quick profile pictures,
pictures for fun, etc., use your imagination , although I don't discuss
that part in this article.
Background
This article explains the basics of Silverlight webcam access: Silverlight web cam access.
The Source Code
I have uploaded the Silverlight project that can be added into Visual Studio straight away.
The Front End
The XAML markup for the front end looks like this:
<UserControl x:Class="SilverlightApplication1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="800" d:DesignWidth="900">
<Grid x:Name="LayoutRoot" Background="White">
<Button Content="Activate Camera" Height="23" HorizontalAlignment="Left"
Margin="156,20,0,0" Name="btnActivate"
VerticalAlignment="Top" Width="100" Click="btnActivate_Click" />
<Rectangle Height="239" HorizontalAlignment="Left"
Margin="12,49,0,0" Name="rectangle1" Stroke="Black"
StrokeThickness="1" VerticalAlignment="Top" Width="376" />
<Rectangle Height="239" HorizontalAlignment="Left" Margin="409,49,0,0"
Name="rectangle2" Stroke="Black" StrokeThickness="1"
VerticalAlignment="Top" Width="376" />
<Button Content="Freeze Frame" Height="23" HorizontalAlignment="Left"
Margin="559,20,0,0" Name="btnFreeze" VerticalAlignment="Top"
Width="100" Click="btnFreeze_Click" />
<Image Height="183" HorizontalAlignment="Left" Margin="409,305,0,0"
Name="image1" Stretch="Fill" VerticalAlignment="Top" Width="376" />
<Button Name="btnSave" Content="Save Shot" Click="btnSave_Click"
Height="23" HorizontalAlignment="Left" Width="85" Margin="500,200,0,0"></Button>
</Grid>
</UserControl>
This is what the GUI looks like, rather simple. On the left is the web cam feed live, on the right hand side is a frame captured. (My hand moved slightly after capturing
the frame and taking the screen grab, so my hand appears lower on the left than on the right).
Clicking on "Activate Camera" starts the live video feed preview in the left box, clicking on "Freeze Frame" does just that,
freezes a frame from the live video and shows in the right box. Clicking "Save Shot" saves the frozen frame as a PNG image on to disk. That's all.
The C# code behind
The C# code that does the capture and save is discussed in the following phases:
- Getting access to webcam
As you can see below, the first step is to get the video capture device and stream its feed into a pre-viewing Rectangle
i.e., rectangle1
.
This is done by painting or "Fill"ing rectangle1
by a VideoBrush
whose content source has been set to the video capture device.
Keep in mind also that rectangle2
has been filled with an image brush the reason for which will be discussed shortly.
Once that's done, the RequestDeviceAccess()
method asks for user permission to access the webcam, which if gets approved starts the webcam and live-feed appears
in rectangle1
.
private void btnActivate_Click(object sender, RoutedEventArgs e)
{
source = new CaptureSource();
VideoCaptureDevice vcd = CaptureDeviceConfiguration.GetDefaultVideoCaptureDevice();
source.VideoCaptureDevice = vcd;
VideoBrush vb = new VideoBrush();
ib = new ImageBrush();
vb.SetSource(source);
rectangle1.Fill = vb;
rectangle2.Fill = ib;
if (CaptureDeviceConfiguration.RequestDeviceAccess())
{
source.Start();
}
}
- Freezing a frame
Attach the CaptureImageCompleted
event handler to the click event of the "Freeze Frame" button like so:
private void btnFreeze_Click(object sender, RoutedEventArgs e)
{
source.CaptureImageCompleted += new EventHandler<CaptureImageCompletedEventArgs>(source_CaptureImageCompleted);
source.CaptureImageAsync();
}
and call the CaptureImageAsync()
method which is an asynchronous method which fires and continues with the rest of the code without having
to block and wait for a response. When the method returns, the event handler gets fired and the result is collected like so:
void source_CaptureImageCompleted(object sender, CaptureImageCompletedEventArgs e)
{
ib.ImageSource = e.Result;
CapturedImage = e.Result;
}
If you remember, moments ago we had filled rectangle2
with an image brush "ib
" and at that point it was empty i.e., it had no source
to paint an image from. Well, now it does. As can be seen above, I am setting the ImageSource
of
ImageBrush
"ib
" to be the result
of the CaptureImageCompleted
event. This results in a "frame" being frozen from the live video feed and appearing in rectangle2
as shown
in the GUI screenshot previously.
Also notice, I am setting another property, CapturedImage
with the same result. What about it? Well, CapturedImage
is
defined as follows:
private WriteableBitmap _capimage;
public WriteableBitmap CapturedImage
{
get
{
return _capimage;
}
set { _capimage = value; }
}
It is a WriteableBitmap
object that will help in saving the frame to an actual image which is what I talk about in the penultimate phase.
- Saving the frame to PNG
Now, this is where you are going to need some third party open source library support. ImageTools is just
the library for this job. It provides Encoders/Decoders for a couple of image formats, PNG being one of them.
You will need to reference the following DLLs in your Silverlight project:
- ImageTools.dll
- ImageTools.IO.PNG.dll
- ImageTools.Utils.dll
Include the following namespaces as well if not already there:
using System.Windows.Media;
using System.Windows.Media.Effects;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
ImageTools also provides an extension method: ToImage(WriteableBitmap wmp)
which converts
a WriteableBitmap
object
to an ExtendedImage
object (part of the ImageTools library).
Save this frame to PNG on the click of "Save Shot" button like so:
private void btnSave_Click(object sender, RoutedEventArgs e)
{
SaveFileDialog sfd = new SaveFileDialog();
sfd.Filter = "PNG files (*.PNG)|*.png|All Files (*.*)|*.*";
var enc = new PngEncoder();
if ((bool)sfd.ShowDialog())
{
Stream stream = sfd.OpenFile();
var image = Imager.ToImg(CapturedImage);
enc.Encode(image, stream);
stream.Close();
}
}
[N.B.] : I, for some unknown reason, could not access the ToImage()
extension method directly so I de-compiled
the ImageTools DLL using IL-Spy and copied
the ToImage()
method to a separate class in my Silverlight project and named it ToImg()
. I just used the original method "as-is"
and did not modify it in anyway, its original authors are the guys who built the ImageTools library not me.
Just so you know .
Anyway, getting back, as you can see above, I am opening a SaveFileDialog
to create an empty PNG file, open it into a Stream
, convert
the CapturedImage
i.e.n WriteableBitmap
to an ExtendedImage
typen and then call
the Encode()
method defined
in the PngEncoder
class in ImageTools.IO.PNG.dll to encode the image into PNG format and write back to the stream. Since the stream is directly connected with
the actual file, the encoded data gets written to the file as soon as the stream is closed and
voila! You have successfully captured an image from your webcam
in a Silverlight web app into a PNG file.
Points of Interest
I would be honest, I did try some weird hacks to write my own PngEncoder (this was way before I had even heard of ImageTools and the "build-my-own" desire was at toxic levels
) but I couldn't really make much headway into it. Finally, I just decided
to bite the bullet and go for the third party library way. But studying this library, one can begin to understand how various image formats work and how
to approach writing an encoder for them.
The next steps for this code could be to wrap it into a nice Silverlight user control that can be plugged
into any application and it will work right out of the box (though that remains to be seen).
If it gets too complex for a control then it can even
remain as a standalone application that can then be embedded into another Silverlight application and loaded dynamically from the host app. I discuss how to do that in my blog
here.
As to the use case of such a control, I am sure people can use their imaginations and find ways to use it. I have already mentioned a couple off the top
of my head in the beginning of this article but they are by no means all.
Thanks for considering this article worth your reading.