教你如何用Silverlight调用摄像头和麦克风,拍照,保存照片
在这个新的Silverlight一节中我会告诉你如何使用新的摄像头API来创建一个非常酷的摄像头应用程序,允许你运行你的摄像头,采取有趣的物体,如帽子快照图像,放在你的脸上,采取快照,然后保存图像到硬盘驱动器。
1. PNG格式操作类,图像保存为PHG格式.PngEncoder.cs
/// <summary>
/// PNG格式操作类
/// </summary>
public class PngEncoder
{
private const int _ADLER32_BASE = 65521;
private const int _MAXBLOCK = 0xFFFF;
private static byte[] _HEADER = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
private static byte[] _IHDR = { (byte)'I', (byte)'H', (byte)'D', (byte)'R' };
private static byte[] _GAMA = { (byte)'g', (byte)'A', (byte)'M', (byte)'A' };
private static byte[] _IDAT = { (byte)'I', (byte)'D', (byte)'A', (byte)'T' };
private static byte[] _IEND = { (byte)'I', (byte)'E', (byte)'N', (byte)'D' };
private static byte[] _4BYTEDATA = { 0, 0, 0, 0 };
private static byte[] _ARGB = { 0, 0, 0, 0, 0, 0, 0, 0, 8, 6, 0, 0, 0 };
/// <summary>
/// 编码
/// </summary>
/// <param name="data"></param>
/// <param name="width"></param>
/// <param name="height"></param>
/// <returns></returns>
public static Stream Encode(byte[] data, int width, int height)
{
MemoryStream ms = new MemoryStream();
byte[] size;
// Write PNG header
ms.Write(_HEADER, 0, _HEADER.Length);
// Write IHDR
// Width: 4 bytes
// Height: 4 bytes
// Bit depth: 1 byte
// Color type: 1 byte
// Compression method: 1 byte
// Filter method: 1 byte
// Interlace method: 1 byte
size = BitConverter.GetBytes(width);
_ARGB[0] = size[3]; _ARGB[1] = size[2]; _ARGB[2] = size[1]; _ARGB[3] = size[0];
size = BitConverter.GetBytes(height);
_ARGB[4] = size[3]; _ARGB[5] = size[2]; _ARGB[6] = size[1]; _ARGB[7] = size[0];
// Write IHDR chunk
WriteChunk(ms, _IHDR, _ARGB);
// Set gamma = 1
size = BitConverter.GetBytes(1 * 100000);
_4BYTEDATA[0] = size[3]; _4BYTEDATA[1] = size[2]; _4BYTEDATA[2] = size[1]; _4BYTEDATA[3] = size[0];
// Write gAMA chunk
WriteChunk(ms, _GAMA, _4BYTEDATA);
// Write IDAT chunk
uint widthLength = (uint)(width * 4) + 1;
uint dcSize = widthLength * (uint)height;
// First part of ZLIB header is 78 1101 1010 (DA) 0000 00001 (01)
// ZLIB info
//
// CMF Byte: 78
// CINFO = 7 (32K window size)
// CM = 8 = (deflate compression)
// FLG Byte: DA
// FLEVEL = 3 (bits 6 and 7 - ignored but signifies max compression)
// FDICT = 0 (bit 5, 0 - no preset dictionary)
// FCHCK = 26 (bits 0-4 - ensure CMF*256+FLG / 31 has no remainder)
// Compressed data
// FLAGS: 0 or 1
// 00000 00 (no compression) X (X=1 for last block, 0=not the last block)
// LEN = length in bytes (equal to ((width*4)+1)*height
// NLEN = one's compliment of LEN
// Example: 1111 1011 1111 1111 (FB), 0000 0100 0000 0000 (40)
// Data for each line: 0 [RGBA] [RGBA] [RGBA] ...
// ADLER32
uint adler = ComputeAdler32(data);
MemoryStream comp = new MemoryStream();
// 64K的块数计算
uint rowsPerBlock = _MAXBLOCK / widthLength;
uint blockSize = rowsPerBlock * widthLength;
uint blockCount;
ushort length;
uint remainder = dcSize;
if ((dcSize % blockSize) == 0)
{
blockCount = dcSize / blockSize;
}
else
{
blockCount = (dcSize / blockSize) + 1;
}
// 头部
comp.WriteByte(0x78);
comp.WriteByte(0xDA);
for (uint blocks = 0; blocks < blockCount; blocks++)
{
// 长度
length = (ushort)((remainder < blockSize) ? remainder : blockSize);
if (length == remainder)
{
comp.WriteByte(0x01);
}
else
{
comp.WriteByte(0x00);
}
comp.Write(BitConverter.GetBytes(length), 0, 2);
comp.Write(BitConverter.GetBytes((ushort)~length), 0, 2);
// Write 块
comp.Write(data, (int)(blocks * blockSize), length);
//下一块
remainder -= blockSize;
}
WriteReversedBuffer(comp, BitConverter.GetBytes(adler));
comp.Seek(0, SeekOrigin.Begin);
byte[] dat = new byte[comp.Length];
comp.Read(dat, 0, (int)comp.Length);
WriteChunk(ms, _IDAT, dat);
// Write IEND chunk
WriteChunk(ms, _IEND, new byte[0]);
// Reset stream
ms.Seek(0, SeekOrigin.Begin);
return ms;
}
private static void WriteReversedBuffer(Stream stream, byte[] data)
{
int size = data.Length;
byte[] reorder = new byte[size];
for (int idx = 0; idx < size; idx++)
{
reorder[idx] = data[size - idx - 1];
}
stream.Write(reorder, 0, size);
}
private static void WriteChunk(Stream stream, byte[] type, byte[] data)
{
int idx;
int size = type.Length;
byte[] buffer = new byte[type.Length + data.Length];
// 初始化缓冲
for (idx = 0; idx < type.Length; idx++)
{
buffer[idx] = type[idx];
}
for (idx = 0; idx < data.Length; idx++)
{
buffer[idx + size] = data[idx];
}
WriteReversedBuffer(stream, BitConverter.GetBytes(data.Length));
// Write 类型和数据
stream.Write(buffer, 0, buffer.Length); // Should always be 4 bytes
// 计算和书写的CRC
WriteReversedBuffer(stream, BitConverter.GetBytes(GetCRC(buffer)));
}
private static uint[] _crcTable = new uint[256];
private static bool _crcTableComputed = false;
private static void MakeCRCTable()
{
uint c;
for (int n = 0; n < 256; n++)
{
c = (uint)n;
for (int k = 0; k < 8; k++)
{
if ((c & (0x00000001)) > 0)
c = 0xEDB88320 ^ (c >> 1);
else
c = c >> 1;
}
_crcTable[n] = c;
}
_crcTableComputed = true;
}
private static uint UpdateCRC(uint crc, byte[] buf, int len)
{
uint c = crc;
if (!_crcTableComputed)
{
MakeCRCTable();
}
for (int n = 0; n < len; n++)
{
c = _crcTable[(c ^ buf[n]) & 0xFF] ^ (c >> 8);
}
return c;
}
//返回的字节的CRC缓冲区
private static uint GetCRC(byte[] buf)
{
return UpdateCRC(0xFFFFFFFF, buf, buf.Length) ^ 0xFFFFFFFF;
}
private static uint ComputeAdler32(byte[] buf)
{
uint s1 = 1;
uint s2 = 0;
int length = buf.Length;
for (int idx = 0; idx < length; idx++)
{
s1 = (s1 + (uint)buf[idx]) % _ADLER32_BASE;
s2 = (s2 + s1) % _ADLER32_BASE;
}
return (s2 << 16) + s1;
}
}
2.图片处理.EditableImage.cs
/// <summary>
/// 编辑图片
/// </summary>
public class EditableImage
{
private int _width = 0;
private int _height = 0;
private bool _init = false;
private byte[] _buffer;
private int _rowLength;
/// <summary>
/// 当图片错误时引发
/// </summary>
public event EventHandler<EditableImageErrorEventArgs> ImageError;
/// <summary>
/// 实例化
/// </summary>
/// <param name="width"></param>
/// <param name="height"></param>
public EditableImage(int width, int height)
{
this.Width = width;
this.Height = height;
}
public int Width
{
get
{
return _width;
}
set
{
if (_init)
{
OnImageError("错误: 图片初始化后不可以改变宽度");
}
else if ((value <= 0) || (value > 2047))
{
OnImageError("错误: 宽度必须在 0 到 2047");
}
else
{
_width = value;
}
}
}
public int Height
{
get
{
return _height;
}
set
{
if (_init)
{
OnImageError("错误: 图片初始化后不可以改变高度");
}
else if ((value <= 0) || (value > 2047))
{
OnImageError("错误: 高度必须在 0 到 2047");
}
else
{
_height = value;
}
}
}
public void SetPixel(int col, int row, Color color)
{
SetPixel(col, row, color.R, color.G, color.B, color.A);
}
public void SetPixel(int col, int row, byte red, byte green, byte blue, byte alpha)
{
if (!_init)
{
_rowLength = _width * 4 + 1;
_buffer = new byte[_rowLength * _height];
// Initialize
for (int idx = 0; idx < _height; idx++)
{
_buffer[idx * _rowLength] = 0; // Filter bit
}
_init = true;
}
if ((col > _width) || (col < 0))
{
OnImageError("Error: Column must be greater than 0 and less than the Width");
}
else if ((row > _height) || (row < 0))
{
OnImageError("Error: Row must be greater than 0 and less than the Height");
}
// Set the pixel
int start = _rowLength * row + col * 4 + 1;
_buffer[start] = red;
_buffer[start + 1] = green;
_buffer[start + 2] = blue;
_buffer[start + 3] = alpha;
}
public Color GetPixel(int col, int row)
{
if ((col > _width) || (col < 0))
{
OnImageError("Error: Column must be greater than 0 and less than the Width");
}
else if ((row > _height) || (row < 0))
{
OnImageError("Error: Row must be greater than 0 and less than the Height");
}
Color color = new Color();
int _base = _rowLength * row + col + 1;
color.R = _buffer[_base];
color.G = _buffer[_base + 1];
color.B = _buffer[_base + 2];
color.A = _buffer[_base + 3];
return color;
}
public Stream GetStream()
{
Stream stream;
if (!_init)
{
OnImageError("Error: Image has not been initialized");
stream = null;
}
else
{
stream = PngEncoder.Encode(_buffer, _width, _height);
}
return stream;
}
private void OnImageError(string msg)
{
if (null != ImageError)
{
EditableImageErrorEventArgs args = new EditableImageErrorEventArgs();
args.ErrorMessage = msg;
ImageError(this, args);
}
}
public class EditableImageErrorEventArgs : EventArgs
{
private string _errorMessage = string.Empty;
public string ErrorMessage
{
get { return _errorMessage; }
set { _errorMessage = value; }
}
}
}
3.应用
前台
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" xmlns:il="clr-namespace:Microsoft.Expression.Interactivity.Layout;assembly=Microsoft.Expression.Interactions" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Class="WeccamDemo.MainPage"
Width="640" Height="480" mc:Ignorable="d">
<Grid x:Name="LayoutRoot">
<Grid.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FF760085" Offset="0"/>
<GradientStop Color="White" Offset="1"/>
</LinearGradientBrush>
</Grid.Background>
<Canvas x:Name="SnapContainer" Background="#FFA3B4FF" Margin="120,21,140,206">
<Rectangle x:Name="CameraCanvas" Fill="White" Height="160" Canvas.Left="96" Stroke="Black" Canvas.Top="86" Width="200" d:LayoutOverrides="Width, Height"/>
<Path x:Name="hat" Data="M555,375 C532.33331,375 509.66666,375 487,375 C463.2756,375 443.98489,380.12689 421,383 C420.00772,383.12402 419,383 418,383 C417,383 415.99747,383.07126 415,383 C412.3223,382.80875 405.94312,391.07962 416,377 C418.44412,373.57822 426.32227,373.47314 432,373 C444.02383,371.99802 454.69058,376.15695 462,363 C469.42038,349.64331 469.92422,333.37888 473,318 C489.43094,318 503.58987,321 520,321 C520,333.02835 511.57379,348.32788 525,356 C539.55701,364.3183 533.65735,369 555,369 C555,371.50333 554,373.49667 554,376 C554.33331,375.66666 554.66669,375.33334 555,375 z" Fill="#FF310808" Height="68.241" Canvas.Left="96" Stretch="Fill" Stroke="Black" Canvas.Top="297" UseLayoutRounding="False" Width="145.469">
<i:Interaction.Behaviors>
<il:MouseDragElementBehavior/>
</i:Interaction.Behaviors>
</Path>
</Canvas>
<Button x:Name="StartCamBtn" Content="开启视频" HorizontalAlignment="Left" Height="29" Margin="8,21,0,0" VerticalAlignment="Top" Width="108"/>
<Button x:Name="TakeSnapBtn" Content="拍照" HorizontalAlignment="Left" Height="29" Margin="8,54,0,0" VerticalAlignment="Top" Width="108"/>
<Button x:Name="SaveBtn" Content="保存照片" HorizontalAlignment="Left" Height="29" Margin="8,87,0,0" VerticalAlignment="Top" Width="108"/>
<Canvas x:Name="ImageHolder" Margin="82,0,24,-49" Height="255" VerticalAlignment="Bottom"/>
</Grid>
</UserControl>
后台
public partial class MainPage : UserControl
{
private CaptureSource CapSrc = new CaptureSource();
private VideoBrush MyVideoBrush = new VideoBrush();
private WriteableBitmap _bitmap;
private bool _VideoIsPlaying = false;
public MainPage()
{
InitializeComponent();
Loaded += new RoutedEventHandler(MainPage_Loaded);
}
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
StartCamBtn.Click += new RoutedEventHandler(StartCamBtn_Click);
TakeSnapBtn.Click += new RoutedEventHandler(TakeSnapBtn_Click);
SaveBtn.Click += new RoutedEventHandler(SaveBtn_Click);
}
void SaveBtn_Click(object sender, RoutedEventArgs e)
{
SaveTheImg();
}
/// <summary>
/// 保存图片
/// </summary>
private void SaveTheImg()
{
_bitmap = new WriteableBitmap(ImageHolder, null);
SaveFileDialog sfd = new SaveFileDialog();
sfd.Filter = "PNG Files (*.png)|*.png|All Files (*.*)|*.*";
sfd.DefaultExt = ".png";
sfd.FilterIndex = 1;
if ((bool)sfd.ShowDialog())
{
using (Stream fs = sfd.OpenFile())
{
int width = _bitmap.PixelWidth;
int height = _bitmap.PixelHeight;
EditableImage ei = new EditableImage(width, height);
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
int pixel = _bitmap.Pixels[(i * width) + j];
ei.SetPixel(j, i,
(byte)((pixel >> 16) & 0xFF),
(byte)((pixel >> 8) & 0xFF),
(byte)(pixel & 0xFF),
(byte)((pixel >> 24) & 0xFF)
);
}
}
//获取流
Stream png = ei.GetStream();
int len = (int)png.Length;
byte[] bytes = new byte[len];
png.Read(bytes, 0, len);
fs.Write(bytes, 0, len);
}
}
}
void TakeSnapBtn_Click(object sender, RoutedEventArgs e)
{
WriteableBitmap snapshot = new WriteableBitmap(SnapContainer, null);
Image image = new Image();
image.Width = 400;
image.Source = snapshot;
if (ImageHolder.Children.Count == 1)
{
ImageHolder.Children.RemoveAt(0);
}
ImageHolder.Children.Add(image);
}
void StartCamBtn_Click(object sender, RoutedEventArgs e)
{
if (!_VideoIsPlaying)
{
_VideoIsPlaying = true;
//调用
if (CaptureDeviceConfiguration.AllowedDeviceAccess == true || CaptureDeviceConfiguration.RequestDeviceAccess())
{
MyVideoBrush.SetSource(CapSrc);
CameraCanvas.Fill = MyVideoBrush;
CapSrc.Start();
}
}
else
{
_VideoIsPlaying = false;
CapSrc.Stop();
}
}
}