Georg fährt extra nach Berlin um Steve Ballmer zu treffen

31 Tage Mango | Tag #7: Rohdaten der Kamera

Dieser Artikel ist Tag #7 der Serie 31 Tage Mango von Jeff Blankenburg.

Der Originalartikel befindet sich hier: Day #7: Raw Camera Data.

Heute beschäftigen wir uns mit der Kamera der Windows Phones und wie wir diese in unseren eigenen Anwendungen verwenden können. Es geht dabei allerdings nicht um Launchers und Choosers. Diese haben wir in 31 Days of Windows Phone am Tag 7 und 8 behandelt. Zur Erinnerung: diese Aktionen erlauben es Ihnen, den Benutzer zur Aufnahme bzw. zur Auswahl eines Foto aus der Sammlung aufzufordern. Heute wollen wir uns damit befassen, wie man die Rohdaten der Kamera anzeigt, wie man ein Bild aus den Rohdaten aufnimmt, wie man die Hardware Buttons benutzt und wie man ein Foto auf dem Telefon des Benutzers abspeichern kann.

Falls Sie die fertige Anwendung dieses Artikels herunterladen möchten, finden Sie diese auch im Windows Phone Marketplace.

Wenn ich von Rohdaten der Kamera rede, meine ich das Live-Bild der Kamera und wie man dieses direkt in der Anwendung verwendet. Am besten lässt sich das wieder an einem kleinen Video zeigen:

Jetzt, da Sie wissen, worum es heute gehen wird, beginnen wir mit etwas Quellcode.

Das Kamerabild anzeigen

Der erste Schritt bei unserer Kamera-Anwendung wird sein, das Live-Bild der Kamera anzuzeigen. Diese Schritt ist zum Glück sehr einfach. Wir erstellen ein Rectangle Control in unserer XAML Seite und setzen die Source dieses Rectangles auf ein neues PhotoCamera Objekt in unserer C# Datei. Unsere XAML Seite sieht damit so aus:

<phone:PhoneApplicationPage
   x:Class="Day7_RawCamera.MainPage"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
   xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
   xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
   FontFamily="{StaticResource PhoneFontFamilyNormal}"
   FontSize="{StaticResource PhoneFontSizeNormal}"
   Foreground="{StaticResource PhoneForegroundBrush}"
   SupportedOrientations="Portrait" Orientation="Portrait"
   shell:SystemTray.IsVisible="True"> 

    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
 
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock x:Name="ApplicationTitle" Text="31 DAYS OF MANGO" Style="{StaticResource PhoneTextNormalStyle}"/>
            <TextBlock x:Name="PageTitle" Text="raw camera" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>
 
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <Rectangle x:Name="ViewBox" Height="460" Margin="-22,-1,-131,148">
                <Rectangle.Fill>
                    <VideoBrush x:Name="CameraSource" />
                </Rectangle.Fill>
                <Rectangle.RenderTransform>
                    <RotateTransform Angle="90" CenterX="240" CenterY="240" />
                </Rectangle.RenderTransform>
            </Rectangle>
        </Grid>
    </Grid>
</phone:PhoneApplicationPage>

Wie Sie im XAML oben sehen, habe ich der Standardseitenvorlage ein Rectangle hinzugefügt. Den Wert der Fill Eigenschaft habe ich auf einen VideoBrush mit dem Namen „CameraSource“ gesetzt. Sie sollten ebenfalls das RenderTransform Element beachten, welches ich auf das Rectangle angewendet habe. Indem wir es um 90 Grad drehen, tragen wir der Tatsache Rechnung, dass die Kamera im Telefon in Landscape Orientierung verbaut ist. Im C# code-behind müssen wir nun die Kameradaten dem VideoBrush zuweisen. Das machen wir, indem wir ein neues PhotoCamera Objekt mit den Namen „camera“ erstellen. Die Source des VideoBrush Objekts setzen wir auf diese PhotoCamera.

using System; 
using System.Windows; 
using Microsoft.Phone.Controls; 
using Microsoft.Devices; 
using System.Windows.Media.Imaging; 
using System.Windows.Media; 

namespace Day7_RawCamera 
{ 
   public partialclass MainPage : PhoneApplicationPage
   { 
      PhotoCamera camera; 

      // Constructor
      public MainPage() 
      { 
         InitializeComponent(); 
      } 

      protectedoverride void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
      { 
         base.OnNavigatedTo(e); 

         if (PhotoCamera.IsCameraTypeSupported(CameraType.FrontFacing)) 
            camera = new PhotoCamera(CameraType.FrontFacing); 
         else
            camera = new PhotoCamera(CameraType.Primary); 
         CameraSource.SetSource(camera); 
      } 

      protectedoverride void OnNavigatingFrom(System.Windows.Navigation.NavigatingCancelEventArgs e) 
      { 
         if (camera != null) 
         { 
            camera.Dispose(); 
         } 
      } 
   } 
} 

Wie ich bereits erwähnt habe, ist das alles vergleichsweise einfach. Es gibt dennoch ein paar wichtige Feinheiten zu beachten. Im Code oben werden Sie festgestellt haben, dass ich bei der Erstellung des PhotoCamera Objekts die Parameter CameraType.Primary und CameraType.FrontFacing verwendet habe. Dadurch gebe ich an, dass ich entweder die Standardkamera auf Rückseite des Telefons oder die nach vorne gerichtete Kamera verwenden möchte. Geräte, die nach dem Start von Mango erschienen sind, haben optional eine nach vorne gerichtete Kamera. In diesem Beispiel verwenden wir vorzugsweise die nach vorne gerichtete Kamera. Nur wenn diese nicht verfügbar ist, verwenden wir die Standardkamera.

Sie werden festgestellt haben, dass ich die OnNavigatedTo und OnNavigatingFrom Handler überschrieben habe. Jedes Mal, wenn der Benutzer zur Seite navigiert (egal ob innerhalb der App oder indem er beispielsweise von einer anderen Anwendung zurückkehrt), soll sichergestellt werden, dass die Kamera initialisiert wird. Die OnNavigatingFrom Methode verwenden wir, um die Kamera freizugeben, wenn der Benutzer die Seite verlässt. Dadurch sparen wir sowohl Akku als auch Hauptspeicher. Zum verantwortungsvollen Umgang mit der Kamera gehört deren Freigabe, wenn wir sie nicht mehr brauchen.

Wir sind jetzt soweit, dass Sie die Anwendung ausprobieren können. Wenn Sie die Anwendung auf einem echten Windows Phone Gerät ausprobieren, werden Sie das sehen, was auch die Kamera sieht, so wie im Video oben. Wenn Sie die Anwendung im Emulator laufen lassen, werden Sie etwa folgendes sehen:

Der Emulator kann leider nicht auf eine Webcam oder andere Videoquellen zugreifen. Statt dessen zeigt er eine weiße Fläche für das Kamerabild. Zudem kreist eine kleine schwarze Fläche um den Bildschirm. Wenn wir hiervon ein Bild aufnehmen, bekommen wir genau den weißen Hintergrund mit der kleinen schwarzen Fläche. Lassen Sie uns das ausprobieren:

Ein Bild aufnehmen

Im nächsten Schritt wollen wir unsere Kameraanwendung so erweitern, dass der Anwender damit ein Bild aufnehmen kann. Hierzu fügen wir der Benutzeroberfläche erst mal einen Button hinzu.

<Button Foreground="Green" BorderBrush="Green" Content="Capture" Height="72" HorizontalAlignment="Left" Margin="6,535,0,0" Name="CaptureButton" VerticalAlignment="Top" Width="160" Click="CaptureButton_Click" />

Ich habe den Button grün gemacht da er vor dem Kamerabild dargestellt wird. Wenn Sie den Emulator benutzen, werden Sie den Button vor dem weißen Hintergrund sonst nicht sehen. Wir haben auch gleich eine Methode für den Click Event angegeben. Darin wollen wir das Telefon veranlassen, ein Bild aufzunehmen.

Jetzt müssen wir ein paar Methoden in der code-behind Datei schreiben. Zunächst schreiben wir die Event Handler Methode für den Button Click Event. Sie enthält eigentlich nur eine Zeile – da die Aufnahme eines Fotos eine aufwändige Operation ist, müssen wir allerdings sicherstellen, dass jede Aufnahme abgeschlossen ist bevor die nächste beginnt. In unserem Beispiel habe ich einfach einen try/catch Block um den Aufruf gesetzt um daraus resultierende Fehler zu vermeiden.

private void CaptureButton_Click(object sender, System.Windows.RoutedEventArgs e)
{
   try { camera.CaptureImage(); }
   catch (Exception ex) { MessageBox.Show(ex.Message); }
} 

Die entscheidende Zeile ist der Aufruf von camera.CaptureImage(). Sie können ja probieren, was passiert, wenn Sie nur diese Zeile verwenden und die Button öfter hintereinander drücken. Die Anwendung wird abstürzen.

Mit der CaptureImage() Methode sagen wir der Kamera nur, dass sie jetzt ein Bild aufnehmen soll. Wenn wir keinen Event Handler erstellen, der das Resultat empfängt, werden wir das Ergebnis nie bekommen. Hierzu verwenden wir den CaptureImageAvailable Event unseres PhotoCamera Objekts.

Damit sie den ganzen Code in Ihre eigene Datei kopieren können, habe ich unten die komplette code-behind Datei eingefügt. Die wichtigen Stellen sind der neue Event Handler in unserer OnNavigatedTo Methode, der camera_CaptureImageAvailable Event Handler und die Methode ThreadSafeImageCapture, welche letztendlich das Ergebnis unserer Aufnahme empfängt. Beachten Sie auch, dass wir alle Event Handler in der Methode OnNavigatedFrom wieder von den entsprechenden Ereignissen lösen. Damit sparen wir Speicher und Akkulaufzeit, wenn die Anwendung im Hintergrund wartet.

using System;
using System.Windows;
using Microsoft.Phone.Controls;
using Microsoft.Devices;
using System.Windows.Media.Imaging;
using System.Windows.Media;

namespace Day7_RawCamera
{
   public partial class MainPage : PhoneApplicationPage
   {
      PhotoCamera camera;

      // Constructor
      public MainPage()
      {
         InitializeComponent();
      }

      protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
      {
         base.OnNavigatedTo(e);

         if (PhotoCamera.IsCameraTypeSupported(CameraType.FrontFacing))
            camera = new PhotoCamera(CameraType.FrontFacing);
         else
            camera = new PhotoCamera(CameraType.Primary);
         camera.CaptureImageAvailable += new System.EventHandler<ContentReadyEventArgs>(camera_CaptureImageAvailable);
         CameraSource.SetSource(camera);
      }

      protected override void OnNavigatingFrom(System.Windows.Navigation.NavigatingCancelEventArgs e)
      {
         if (camera != null)
         {
            camera.Dispose();
            camera.CaptureImageAvailable -= camera_CaptureImageAvailable;
         }
      }

      private void CaptureButton_Click(object sender, System.Windows.RoutedEventArgs e)
      {
         try { camera.CaptureImage(); }
         catch (Exception ex) { Dispatcher.BeginInvoke(() => MessageBox.Show(ex.Message)); }
      }

      void camera_CaptureImageAvailable(object sender, ContentReadyEventArgs e)
      {
         Dispatcher.BeginInvoke(() => ThreadSafeImageCapture(e));
      }

      void ThreadSafeImageCapture(ContentReadyEventArgs e)
      {
         BitmapImage image = new BitmapImage();
         image.SetSource(e.ImageStream);
         ImageBrush still = new ImageBrush();
         still.ImageSource = image;
         ViewBox.Fill = still;
      }
   }
} 

Wie Sie in der Methode ThreadSafeImageCapture sehen, erstellen wir ein neues BitmapImage Objekt (welches ein using System.Windows.Media.Imaging Statement ganz oben benötigt) und setzen die Source dieses BitmapImage auf das Ergebnis unserer Aufnahme.

Die restlichen Schritte in der Methode ersetzen den VideoBrush, den wir zur Anzeige der Rohdaten verwendet haben, um das gerade aufgenommene Standbild anzuzeigen.

Das ist schon mal ein guter Anfang für eine Kameraanwendung. Es gibt aber noch wesentlich mehr zu wissen rund um die Kamera, wie zum Beispiel Verwendung des Hardware Buttons der Kamera (den jedes Windows Phone enthält) oder wie man ein aufgenommenes Bild in den Eigenen Aufnahmen ablegt – also an der Stelle, an der auch die normale Kamera App ihre Fotos speichert.

Den Hardware Button der Kamera benutzen

Der Kamera Hardware Button feuert drei verschiedene Events: ShutterKeyHalfPress, ShutterKeyPressed und ShutterKeyReleased. Wenn wir unsere eigene Kameraanwendung bauen, hat der Button kein Standardverhalten – das müssen wir selber bauen. Da die Anwender höchstwahrscheinlich vertraut sind mit dem Verhalten des Kamerabuttons sollte sich unsere Anwendung bei den drei Events erwartungsgemäß verhalten:

  • ShutterKeyHalfPress – durch diese Aktion fokussiert die Kamera.
  • ShutterKeyPress – durch diese Aktion sollte ein Bild aufgenommen werden.
  • ShutterKeyReleased – durch dieses Ereignis wissen wir, dass nichts mehr zu tun ist. Insbesondere können wir das Fokussieren einstellen.

Als erstes werden wir die drei Ereignisse implementieren. Im folgenden Codebeispiel findet sich nur die Implementierung von OnNavigatedTo – die Event Handler für die einzelnen Ereignisse bauen wir danach.

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
   base.OnNavigatedTo(e);

   if (PhotoCamera.IsCameraTypeSupported(CameraType.FrontFacing))
      camera = new PhotoCamera(CameraType.FrontFacing);
   else
      camera = new PhotoCamera(CameraType.Primary);
   camera.CaptureImageAvailable += new System.EventHandler<ContentReadyEventArgs>(camera_CaptureImageAvailable);
   CameraSource.SetSource(camera);

   CameraButtons.ShutterKeyHalfPressed += new EventHandler(CameraButtons_ShutterKeyHalfPressed);
   CameraButtons.ShutterKeyPressed += new EventHandler(CameraButtons_ShutterKeyPressed);
   CameraButtons.ShutterKeyReleased += new EventHandler(CameraButtons_ShutterKeyReleased);
} 

Als erste Event Handler Methode bauen wir die für den ShutterKeyHalfPressed Event, da sie die aufwändigste ist (obwohl sie nicht wirklich aufwändig ist). Zunächst überprüfen wir, dass unser PhotoCamera Objekt vorhanden ist, dann versuchen wir, die Kamera zu fokussieren. Ich habe den Aufruf in einen try/catch Block eingeschlossen, da wir Focus() nicht aufrufen können, wenn die Kamera gerade eine Aufnahme macht. Eine eventuell auftretende Exception ignorieren wir einfach. Die Exception wird jedes Mal fliegen, wenn Sie eine Aufnahme machen, da der Button auf dem Weg zum „Pressed“ Zustand zwei Mal den „Half-Pressed“ Zustand durchläuft. Einmal auf dem Weg „nach unten“ und einmal, wenn er wieder losgelassen wird. Wenn Sie diesen Fehler in eine Logdatei schreiben wollen, nur zu. Für dieses Beispiel habe ich den catch-Block einfach leer gelassen.

void CameraButtons_ShutterKeyHalfPressed(object sender, EventArgs e)
{
   if (camera != null)
   {
      try { camera.Focus(); }
      catch (Exception ex) { }
   }
} 

Für den ShutterKeyPressed Event verwende ich den Code von vorhin, als ich den Button zur Aufnahme eines Bildes erstellt habe.

void CameraButtons_ShutterKeyPressed(object sender, EventArgs e)
{
   if (camera != null)
   {
      try { camera.CaptureImage(); }
      catch (Exception ex) { Dispatcher.BeginInvoke(() => MessageBox.Show(ex.Message)); }
   }
} 

Zu guter Letzt haben wir noch den ShutterKeyReleased Event. In unserem Fall müssen wir nur dafür sorgen, dass wir nicht weiter fokussieren. CancelFocus() ist die Methode, mit der die Kamera das Fokussieren abbricht.

void CameraButtons_ShutterKeyReleased(object sender, EventArgs e)
{
   if (camera != null)
      camera.CancelFocus();
} 

Zum Schluss stellen wir noch sicher, dass die Event Handler wieder entfernt werden, wenn wir die Seite verlassen. Wie schon beim CaptureImageAvailable Event machen wir das in der Methode OnNavigatingFrom. Ich habe hier den kompletten Code der Methode dargestellt. Neu sind aber nur die unteren drei Zeilen.

protected override void OnNavigatingFrom(System.Windows.Navigation.NavigatingCancelEventArgs e)
{
   if (camera != null)
   {
      camera.Dispose();
      camera.CaptureImageAvailable -= camera_CaptureImageAvailable;
      CameraButtons.ShutterKeyHalfPressed -= CameraButtons_ShutterKeyHalfPressed;
      CameraButtons.ShutterKeyPressed -= CameraButtons_ShutterKeyPressed;
      CameraButtons.ShutterKeyReleased -= CameraButtons_ShutterKeyReleased;
   }
} 

Bilder in Eigene Aufnahmen abspeichern

Hierfür hat Microsoft eine MediaLibrary Klasse erstellt. Wir werden später in dieser Serie eine ganze Anwendung zur Verwendung der MediaLibrary bauen – jetzt wollen wir sie nur benutzen, um unsere Aufnahme in den Eigenen Aufnahmen abzulegen. Dadurch kann der Anwender die Fotos unserer Anwendung genau so verwalten wie alle anderen Fotos auf dem Telefon.

Der gesamte Prozess erfolgt in genau zwei Zeilen Code, wobei die erste Zeile nur darin besteht, eine Referenz auf die MediaLibrary zu erhalten. Ganz oben in der Seite, dort wo wir auch das PhotoCamera Objekt erstellt haben, fügen wir diese Zeile hinzu.

MediaLibrary library = new MediaLibrary(); 

Das war der erste Schritt. Im zweiten Schritt schauen wir uns noch mal die Methode ThreadSafeImageCapture an. Im bisherigen Code haben wir die Aufnahme gemacht, die Eigenschaft Fill von Rectangle geändert und sie auf unsere Aufnahme gesetzt. Für dieses Beispiel enfernen wir den gesamten Methodenrumpf und ersetzen ihn mit:

void ThreadSafeImageCapture(ContentReadyEventArgs e)
{
   library.SavePictureToCameraRoll(DateTime.Now.ToString() + ".jpg", e.ImageStream);
}

Auf einem echten Windows Phone sollten Sie jetzt in der Lage sein, ein Foto aufzunehmen. Dieses sollten Sie im Telefon im Bilder Hub unter Eigene Aufnahmen wieder finden. Hier ist noch mal ein kleines Video um zu illustrieren, worum es geht:

Das war’s im Wesentlichen zur Kamera! Es gibt natürlich viele weitere Dinge, die man mit den Rohdaten der Kamera anstellen kann, z.B. Videos aufnehmen, Manipulation des aufgenommenen Fotos, Änderung der Blitz-Einstellungen usw. Es gibt eine sehr gute Tutorialserie bei der Code Samples for Windows Phone Seite auf MSDN und Sie finden einige Beispiele hier.

Zusammenfassung

Im wahrscheinlich längsten Artikel der Serie haben Sie gelernt, wie man die Rohdaten der Kamera anzeigt, wie man eine Benutzeroberfläche zur Aufnahme eines Bildes erstellt, wie man den Kamera Hardware Button dazu verwenden kann und wie man Fotos in Eigene Aufnahmen speichern kann.

Wenn Sie eine komplett lauffähige Windows Phone Solution mit dem ganzen Beispielcode dieses Artikels herunterladen möchten, klicken Sie auf den Download Code Button.

Morgen werden wir komplett die Richtung ändern und uns einen Teil der Nutzerdaten auf dem Telefon ansehen. Genauer gesagt werden wir uns morgen mit den Kontakten des Benutzers befassen.

Bis dahin!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.