如何为 Windows Phone 创建基本相机应用程序

2012/2/9

使用 Windows Phone OS 7.1,您可以采用编程方式访问设备相机。本主题是介绍如何创建演示使用相机 API 拍摄照片的相机应用程序的系列主题中的第一个主题。本主题演示如何显示相机取景器、如何采用编程方式触发快门以及如何将拍摄的图像保存到媒体库和独立存储。

本主题涉及以下步骤:

  1. 创建相机 UI

  2. 实现基于取景器和相机的事件

  3. 保存到媒体库和独立存储

提示提示:

本主题与基本相机示例相对应。若要下载完整的项目,请参阅 Windows Phone 的代码示例

完成这个基本相机应用程序之后,以下主题并入了诸如实现闪光灯和对焦、更改拍摄分辨率以及使用手机硬件快门按钮之类的功能。继续之前,必须首先完成本主题中的基本相机应用程序。以下主题基于该应用程序构建并且向本主题中实现的功能中添加了功能。

完成所有这些主题之后,完成的解决方案将如下图所示。

AP_Con_CameraAnatomy

在本主题中,只向该应用程序中添加 SH 按钮。闪光灯、自动对焦以及分辨率按钮在本系列的其他主题中添加。

注意注意:

当升级 Windows Phone OS 7.0 应用程序以使用 Windows Phone OS 7.1 中的功能时,相机功能 ID_CAP_ISV_CAMERA 不会自动添加到应用程序清单文件 WMAppManifest.xml 中。如果没有 ID_CAP_ISV_CAMERA,则使用相机 API 的应用程序将无法工作。在新的 Windows Phone OS 7.1 项目中,该功能包含在应用程序的清单文件中。

对于这个版本的 Windows Phone SDK,本主题只能在 Windows Phone 设备上完成,而不能在 Windows Phone 模拟器上完成。

本主题基于 C# 开发;但也提供 Visual Basic 代码。

在本节中,创建相机 UI,它包含一个用于显示所拍摄帧的取景器区域和一个用于拍摄图像的快门按钮。

创建相机 UI 的步骤

  1. 在 Visual Studio 2010 Express for Windows Phone 中,通过选择“文件 | 新建项目”菜单命令创建一个新项目。

  2. 将显示“新建项目”窗口。展开“Visual C#”模板,然后选择“Silverlight for Windows Phone”模板。

  3. 选择“Windows Phone 应用程序”模板。用您选择的名称填写“名称”框。

  4. 单击“确定”。将显示“新建 Windows Phone 应用程序”窗口。

  5. “Windows Phone 目标版本”菜单中,确保已选择 Windows Phone 7.1。

  6. 单击“确定”。将创建一个新的项目,并且“MainPage.xaml”将在 Visual Studio 设计器窗口中打开。

  7. “项目”菜单中,选择“添加引用”。在 .NET 标签中,选择 Microsoft.XNA.Framework 并单击“确定”

  8. MainPage.xaml 上,更新 phone:PhoneApplicationPage 元素,如以下代码中所示。

        SupportedOrientations="Landscape" Orientation="LandscapeLeft"
        shell:SystemTray.IsVisible="False"
    
    

    这将配置横向的页面以及隐藏系统托盘。

  9. MainPage.xaml 中,将名为 LayoutRootGrid 替换为以下代码。

        <!--LayoutRoot is the root grid where all page content is placed-->
        <Grid x:Name="LayoutRoot" Background="Transparent">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="640" />
                <ColumnDefinition Width="160" />
            </Grid.ColumnDefinitions>
            
            <Canvas x:Name="viewfinderCanvas" Width="640" Height="480" 
                       HorizontalAlignment="Left" >
                
                <!--Camera viewfinder -->
                <Canvas.Background>
                    <VideoBrush x:Name="viewfinderBrush" />
                </Canvas.Background>
             </Canvas>
            
            <!--Button StackPanel to the right of viewfinder>-->
            <StackPanel Grid.Column="1" >
                <Button x:Name="ShutterButton" Content="SH" Click="ShutterButton_Click" FontSize="26" FontWeight="ExtraBold" Height="75" />
            </StackPanel>
    
            <!--Used for debugging >-->
            <TextBlock Height="40" HorizontalAlignment="Left" Margin="8,428,0,0" Name="txtDebug" VerticalAlignment="Top" Width="626" FontSize="24" FontWeight="ExtraBold" />
        </Grid>
    
    

    该代码创建一个 640 x 480 取景器区域,该区域具有一个包含快门按钮 SHStackPanel 控件。在以下部分中实现 ShutterButton_Click 事件。

    注意注意:

    本练习中的软件快门按钮只是为了演示采用编程方式访问 PhotoCamera API。若要优化最终用户的体验,我们建议您的应用程序使用相机上的硬件快门按钮。有关如何实现硬件快门按钮的信息,请参阅如何访问 Windows Phone 中的硬件相机快门按钮

  10. 打开主页的代码隐藏文件 MainPage.xaml.cs,在该页面的顶部添加以下指令。

    // Directives
    using Microsoft.Devices;
    using System.IO;
    using System.IO.IsolatedStorage;
    using Microsoft.Xna.Framework.Media;
    
    
  11. MainPage.xaml.csMainPage 类中,在 MainPage 类的构造函数上面添加以下变量声明。

        // Variables
        private int savedCounter = 0;
        PhotoCamera cam;
        MediaLibrary library = new MediaLibrary();
    
    
  12. 若要创建相机应用程序,必须在应用程序清单文件中声明相机功能。如果没有相机功能,该应用程序将无法工作。打开 WMAppManifest.xml 并确认存在以下功能元素。

    <Capability Name="ID_CAP_ISV_CAMERA"/>
    
  13. (可选)如果要使应用程序要求正面相机,请额外将正面相机功能添加到应用程序清单文件中的 Capabilities 元素。

    <Capability Name="ID_HW_FRONTCAMERA"/>
    

    此功能将不会自动添加到新项目,必须手动添加。没有正面相机的用户将收到通知,说明他们的设备不符合您应用程序的要求,不过它们仍然可以选择下载该功能。此应用程序为使用两种相机中的任意一种而设计,因此它不要求该功能。有关更多信息,请参阅 Windows Phone 的相机和照片概述中的功能部分。

若要实现取景器,请将 viewfinderBrush 源设置为 Windows Phone 相机。还实现多个基于相机的事件,如相机初始化、完成拍摄以及图像可用性。

实现基于取景器和相机的事件

  1. MainPage.xaml.cs 中,向 MainPage 类中添加以下代码。

    注意注意:

    直到您完成此过程中的以下步骤,Visual Studio 才会列出有关当前上下文中不存在的方法的错误。将在以下步骤中添加这些方法。

            //Code for initialization, capture completed, image availability events; also setting the source for the viewfinder.
            protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
            {
    
                // Check to see if the camera is available on the device.
                if ((PhotoCamera.IsCameraTypeSupported(CameraType.Primary) == true) ||
                     (PhotoCamera.IsCameraTypeSupported(CameraType.FrontFacing) == true))
                {
                    // Initialize the camera, when available.
                    if (PhotoCamera.IsCameraTypeSupported(CameraType.FrontFacing))
                    {
                        // Use front-facing camera if available.
                        cam = new Microsoft.Devices.PhotoCamera(CameraType.FrontFacing);
                    }
                    else
                    {
                        // Otherwise, use standard camera on back of device.
                        cam = new Microsoft.Devices.PhotoCamera(CameraType.Primary);
                    }
    
                    // Event is fired when the PhotoCamera object has been initialized.
                    cam.Initialized += new EventHandler<Microsoft.Devices.CameraOperationCompletedEventArgs>(cam_Initialized);
    
                    // Event is fired when the capture sequence is complete.
                    cam.CaptureCompleted += new EventHandler<CameraOperationCompletedEventArgs>(cam_CaptureCompleted);
    
                    // Event is fired when the capture sequence is complete and an image is available.
                    cam.CaptureImageAvailable += new EventHandler<Microsoft.Devices.ContentReadyEventArgs>(cam_CaptureImageAvailable);
    
                    // Event is fired when the capture sequence is complete and a thumbnail image is available.
                    cam.CaptureThumbnailAvailable += new EventHandler<ContentReadyEventArgs>(cam_CaptureThumbnailAvailable);
    
                    //Set the VideoBrush source to the camera.
                    viewfinderBrush.SetSource(cam);
                }
                else
                {
                    // The camera is not supported on the device.
                    this.Dispatcher.BeginInvoke(delegate()
                    {
                        // Write message.
                        txtDebug.Text = "A Camera is not available on this device.";
                    });
    
                    // Disable UI.
                    ShutterButton.IsEnabled = false;
                }
            }
            protected override void OnNavigatingFrom(System.Windows.Navigation.NavigatingCancelEventArgs e)
            {
                if (cam != null)
                {
                    // Dispose camera to minimize power consumption and to expedite shutdown.
                    cam.Dispose();
    
                    // Release memory, ensure garbage collection.
                    cam.Initialized -= cam_Initialized;
                    cam.CaptureCompleted -= cam_CaptureCompleted;
                    cam.CaptureImageAvailable -= cam_CaptureImageAvailable;
                    cam.CaptureThumbnailAvailable -= cam_CaptureThumbnailAvailable;
                }
            }
    
    

    该代码使用 OnNavigatedTo(NavigationEventArgs) 方法创建名为 camPhotoCamera 对象并配置多个事件。该代码还将 VideoBrush 源设置为手机相机对象 cam

    如果除了主相机之外,设备还有一个正面相机,则应用程序将使用此正面相机。并非所有设备都有正面相机,因此 IsCameraTypeSupported(CameraType) 方法还用于在创建 PhotoCamera 对象之前检查该对象是否存在。还有可能设备没有任何类型的相机。这种情况下,会禁用 UI 并且显示一条消息。

    为了说明 Windows Phone 执行模型,在 OnNavigatedTo(NavigationEventArgs) 方法中初始化 PhotoCamera 对象并在 OnNavigatingFrom(NavigatingCancelEventArgs) 方法期间进行显式释放。在 OnNavigatedTo(NavigationEventArgs) 中添加 PhotoCamera 事件的事件处理程序并在 OnNavigatingFrom(NavigatingCancelEventArgs) 中删除以帮助释放内存。

    注意注意:

    有关如何分析和处理相机预览缓冲区中的单个帧的信息,请参阅如何在 Windows Phone 的相机应用程序中使用灰度

  2. MainPage.xaml.cs 中,向 MainPage 类中添加以下代码。

        // Update the UI if initialization succeeds.
        void cam_Initialized(object sender, Microsoft.Devices.CameraOperationCompletedEventArgs e)
        {
            if (e.Succeeded)
            {
                this.Dispatcher.BeginInvoke(delegate()
                {
                    // Write message.
                    txtDebug.Text = "Camera initialized.";
                });
            }
        }
    
    

    该代码使用相机 Initialized 事件更新名为 txtDebugTextBlock。需要使用 BeginInvoke 方法更新状态,因为应用程序 UI 在另一个线程上运行。

  3. MainPage.xaml.cs 中,向 MainPage 类中添加以下代码。

            // Ensure that the viewfinder is upright in LandscapeRight.
            protected override void OnOrientationChanged(OrientationChangedEventArgs e)
            {
                if (cam != null)
                {
                    // LandscapeRight rotation when camera is on back of device.
                    int landscapeRightRotation = 180;
    
                    // Change LandscapeRight rotation for front-facing camera.
                    if (cam.CameraType == CameraType.FrontFacing) landscapeRightRotation = -180;
    
                    // Rotate video brush from camera.
                    if (e.Orientation == PageOrientation.LandscapeRight)
                    {
                        // Rotate for LandscapeRight orientation.
                        viewfinderBrush.RelativeTransform =
                            new CompositeTransform() { CenterX = 0.5, CenterY = 0.5, Rotation = landscapeRightRotation };
                    }
                    else
                    {
                        // Rotate for standard landscape orientation.
                        viewfinderBrush.RelativeTransform =
                            new CompositeTransform() { CenterX = 0.5, CenterY = 0.5, Rotation = 0 };
                    }
                }
    
                base.OnOrientationChanged(e);
            }
    
    

    该代码确保当相机面朝下(LandscapeRight 方向)时取景器 viewfinderBrush 是直立的。如果相机是正面相机,则需要按照与相机位于设备背面时相反的方向旋转相应的画笔。

    注意注意:

    尽管向任一方向将视频画笔旋转 180 度都会得到相同的结果,但该代码提供了 UI 方向如何基于相机类型的示例。

  4. MainPage.xaml.cs 中,向 MainPage 类中添加以下代码。

            private void ShutterButton_Click(object sender, RoutedEventArgs e)
            {
                if (cam != null)
                {
                    try
                    {
                        // Start image capture.
                        cam.CaptureImage();
                    }
                    catch (Exception ex)
                    {
                        this.Dispatcher.BeginInvoke(delegate()
                        {
                            // Cannot capture an image until the previous capture has completed.
                            txtDebug.Text = ex.Message;
                        });
                    }
                }
            }
    
        void cam_CaptureCompleted(object sender, CameraOperationCompletedEventArgs e)
        {
            // Increments the savedCounter variable used for generating JPEG file names.
            savedCounter++;
        }
    
    

    该代码实现快门按钮和完成拍摄事件。快门按钮是使用 XAML 代码创建的软件按钮,用于拍摄静止图像。在此项目中使用 CaptureCompleted 事件来递增 savedCounter 变量。在下一节中它用于 JPEG 命名。

对于目标为 Windows Phone OS 7.1 的相机应用程序,照相时拍摄两个图像。一个是高分辨率图像,另一个是用于在库视图中提供高分辨率照片的缩略图图像。本节演示如何将高分辨率图像放置到设备媒体库中。还演示如何将高分辨率图像和缩略图图像保存到独立存储。

将图像保存到媒体库和独立存储的步骤

  1. MainPage.xaml.cs 中,向 MainPage 类中添加以下代码。

        // Informs when full resolution picture has been taken, saves to local media library and isolated storage.
        void cam_CaptureImageAvailable(object sender, Microsoft.Devices.ContentReadyEventArgs e)
        {
            string fileName = savedCounter + ".jpg";
    
            try
            {   // Write message to the UI thread.
                Deployment.Current.Dispatcher.BeginInvoke(delegate()
                {
                    txtDebug.Text = "Captured image available, saving picture.";
                });
    
                // Save picture to the library camera roll.
                library.SavePictureToCameraRoll(fileName, e.ImageStream);
    
                // Write message to the UI thread.
                Deployment.Current.Dispatcher.BeginInvoke(delegate()
                {
                    txtDebug.Text = "Picture has been saved to camera roll.";
    
                });
    
                // Set the position of the stream back to start
                e.ImageStream.Seek(0, SeekOrigin.Begin);
    
                // Save picture as JPEG to isolated storage.
                using (IsolatedStorageFile isStore = IsolatedStorageFile.GetUserStoreForApplication())
                {
                    using (IsolatedStorageFileStream targetStream = isStore.OpenFile(fileName, FileMode.Create, FileAccess.Write))
                    {
                        // Initialize the buffer for 4KB disk pages.
                        byte[] readBuffer = new byte[4096];
                        int bytesRead = -1;
    
                        // Copy the image to isolated storage. 
                        while ((bytesRead = e.ImageStream.Read(readBuffer, 0, readBuffer.Length)) > 0)
                        {
                            targetStream.Write(readBuffer, 0, bytesRead);
                        }
                    }
                }
    
                // Write message to the UI thread.
                Deployment.Current.Dispatcher.BeginInvoke(delegate()
                {
                    txtDebug.Text = "Picture has been saved to isolated storage.";
    
                });
            }
            finally
            {
                // Close image stream
                e.ImageStream.Close();
            }
    
        }
    
        // Informs when thumbnail picture has been taken, saves to isolated storage
        // User will select this image in the pictures application to bring up the full-resolution picture. 
        public void cam_CaptureThumbnailAvailable(object sender, ContentReadyEventArgs e)
        {
            string fileName = savedCounter + "_th.jpg";
    
            try
            {
                // Write message to UI thread.
                Deployment.Current.Dispatcher.BeginInvoke(delegate()
                {
                    txtDebug.Text = "Captured image available, saving thumbnail.";
                });
    
                // Save thumbnail as JPEG to isolated storage.
                using (IsolatedStorageFile isStore = IsolatedStorageFile.GetUserStoreForApplication())
                {
                    using (IsolatedStorageFileStream targetStream = isStore.OpenFile(fileName, FileMode.Create, FileAccess.Write))
                    {
                        // Initialize the buffer for 4KB disk pages.
                        byte[] readBuffer = new byte[4096];
                        int bytesRead = -1;
    
                        // Copy the thumbnail to isolated storage. 
                        while ((bytesRead = e.ImageStream.Read(readBuffer, 0, readBuffer.Length)) > 0)
                        {
                            targetStream.Write(readBuffer, 0, bytesRead);
                        }
                    }
                }
    
                // Write message to UI thread.
                Deployment.Current.Dispatcher.BeginInvoke(delegate()
                {
                    txtDebug.Text = "Thumbnail has been saved to isolated storage.";
    
                });
            }
            finally
            {
            // Close image stream
            e.ImageStream.Close();
            }
        }
    
    

    该代码实现 CaptureImageAvailableCaptureThumbnailAvailable 事件。第一个方法将高分辨率图像保存到设备媒体库和独立存储。第二个方法演示如何将缩略图图像保存到独立存储。

    注意注意:

    若要将图片保存到图片中心的“保存的图片”相册中,请使用 SavePicture 方法。此示例中使用的 SavePictureToCameraRoll 方法将图像保存到“本机拍照”相册中。

  2. 在设备上,通过选择“调试 | 启动调试”菜单命令来运行应用程序。通过按 SH 按钮测试应用程序。txtDebug TextBlock 将指示保存到独立存储的保存操作的状态。退出应用程序之后,您可以在图片中心的“本机拍照”文件夹中找到使用该应用程序拍摄的照片。

    以下示例演示此时如何显示 UI。

    AP_Con_CameraWireFrame

    在此示例中,软件快门按钮 SH 显示在 UI 的右上角。Mango 的照片出现在名为 viewfinderCanvasCanvas 控件中。

  3. 既然您已完成了这个基本相机应用程序,那么您可以使用它来完成以下主题:

显示: