31 Tage Mango | Tag #7: Rohdaten der Kamera

Geschrieben am von leitning

Die­ser Arti­kel ist Tag #7 der Serie 31 Tage Mango von Jeff Blankenburg.

Der Ori­gi­nal­ar­ti­kel befin­det sich hier: Day #7: Raw Camera Data.

Heute beschäf­ti­gen wir uns mit der Kamera der Win­dows Pho­nes und wie wir diese in unse­ren eige­nen Anwen­dun­gen ver­wen­den kön­nen. Es geht dabei aller­dings nicht um Laun­chers und Choo­sers. Diese haben wir in 31 Days of Win­dows Phone am Tag 7 und 8 behan­delt. Zur Erin­ne­rung: diese Aktio­nen erlau­ben es Ihnen, den Benut­zer zur Auf­nahme bzw. zur Aus­wahl eines Foto aus der Samm­lung auf­zu­for­dern. Heute wol­len wir uns damit befas­sen, wie man die Roh­da­ten der Kamera anzeigt, wie man ein Bild aus den Roh­da­ten auf­nimmt, wie man die Hard­ware But­tons benutzt und wie man ein Foto auf dem Tele­fon des Benut­zers abspei­chern kann.

Falls Sie die fer­tige Anwen­dung die­ses Arti­kels her­un­ter­la­den möch­ten, fin­den Sie diese auch im Win­dows Phone Marketplace.

Wenn ich von Roh­da­ten der Kamera rede, meine ich das Live-Bild der Kamera und wie man die­ses direkt in der Anwen­dung ver­wen­det. Am bes­ten lässt sich das wie­der an einem klei­nen Video zeigen:

Jetzt, da Sie wis­sen, worum es heute gehen wird, begin­nen wir mit etwas Quellcode.

Das Kame­ra­bild anzeigen

Der erste Schritt bei unse­rer Kamera-Anwendung wird sein, das Live-Bild der Kamera anzu­zei­gen. Diese Schritt ist zum Glück sehr ein­fach. Wir erstel­len ein Rec­tangle Con­trol in unse­rer XAML Seite und set­zen die Source die­ses Rec­tan­g­les auf ein neues Pho­to­Ca­mera Objekt in unse­rer 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 Stan­dard­sei­ten­vor­lage ein Rec­tangle hin­zu­ge­fügt. Den Wert der Fill Eigen­schaft habe ich auf einen VideoBrush mit dem Namen „Came­ra­Source“ gesetzt. Sie soll­ten eben­falls das Ren­der­Trans­form Ele­ment beach­ten, wel­ches ich auf das Rec­tangle ange­wen­det habe. Indem wir es um 90 Grad dre­hen, tra­gen wir der Tat­sa­che Rech­nung, dass die Kamera im Tele­fon in Land­scape Ori­en­tie­rung ver­baut ist. Im C# code-behind müs­sen wir nun die Kame­ra­da­ten dem VideoBrush zuwei­sen. Das machen wir, indem wir ein neues Pho­to­Ca­mera Objekt mit den Namen „camera“ erstel­len. Die Source des VideoBrush Objekts set­zen 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 ver­gleichs­weise ein­fach. Es gibt den­noch ein paar wich­tige Fein­hei­ten zu beach­ten. Im Code oben wer­den Sie fest­ge­stellt haben, dass ich bei der Erstel­lung des Pho­to­Ca­mera Objekts die Para­me­ter CameraType.Primary und CameraType.FrontFacing ver­wen­det habe. Dadurch gebe ich an, dass ich ent­we­der die Stan­dard­ka­mera auf Rück­seite des Tele­fons oder die nach vorne gerich­tete Kamera ver­wen­den möchte. Geräte, die nach dem Start von Mango erschie­nen sind, haben optio­nal eine nach vorne gerich­tete Kamera. In die­sem Bei­spiel ver­wen­den wir vor­zugs­weise die nach vorne gerich­tete Kamera. Nur wenn diese nicht ver­füg­bar ist, ver­wen­den wir die Standardkamera.

Sie wer­den fest­ge­stellt haben, dass ich die OnNa­vi­ga­tedTo und OnNa­vi­ga­ting­From Hand­ler über­schrie­ben habe. Jedes Mal, wenn der Benut­zer zur Seite navi­giert (egal ob inner­halb der App oder indem er bei­spiels­weise von einer ande­ren Anwen­dung zurück­kehrt), soll sicher­ge­stellt wer­den, dass die Kamera initia­li­siert wird. Die OnNa­vi­ga­ting­From Methode ver­wen­den wir, um die Kamera frei­zu­ge­ben, wenn der Benut­zer die Seite ver­lässt. Dadurch spa­ren wir sowohl Akku als auch Haupt­spei­cher. Zum ver­ant­wor­tungs­vol­len Umgang mit der Kamera gehört deren Frei­gabe, wenn wir sie nicht mehr brauchen.

Wir sind jetzt soweit, dass Sie die Anwen­dung aus­pro­bie­ren kön­nen. Wenn Sie die Anwen­dung auf einem ech­ten Win­dows Phone Gerät aus­pro­bie­ren, wer­den Sie das sehen, was auch die Kamera sieht, so wie im Video oben. Wenn Sie die Anwen­dung im Emu­la­tor lau­fen las­sen, wer­den Sie etwa fol­gen­des sehen:

Der Emu­la­tor kann lei­der nicht auf eine Web­cam oder andere Video­quel­len zugrei­fen. Statt des­sen zeigt er eine weiße Flä­che für das Kame­ra­bild. Zudem kreist eine kleine schwarze Flä­che um den Bild­schirm. Wenn wir hier­von ein Bild auf­neh­men, bekom­men wir genau den wei­ßen Hin­ter­grund mit der klei­nen schwar­zen Flä­che. Las­sen Sie uns das ausprobieren:

Ein Bild aufnehmen

Im nächs­ten Schritt wol­len wir unsere Kame­ra­an­wen­dung so erwei­tern, dass der Anwen­der damit ein Bild auf­neh­men kann. Hierzu fügen wir der Benut­zer­ober­flä­che erst mal einen But­ton 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 But­ton grün gemacht da er vor dem Kame­ra­bild dar­ge­stellt wird. Wenn Sie den Emu­la­tor benut­zen, wer­den Sie den But­ton vor dem wei­ßen Hin­ter­grund sonst nicht sehen. Wir haben auch gleich eine Methode für den Click Event ange­ge­ben. Darin wol­len wir das Tele­fon ver­an­las­sen, ein Bild aufzunehmen.

Jetzt müs­sen wir ein paar Metho­den in der code-behind Datei schrei­ben. Zunächst schrei­ben wir die Event Hand­ler Methode für den But­ton Click Event. Sie ent­hält eigent­lich nur eine Zeile — da die Auf­nahme eines Fotos eine auf­wän­dige Ope­ra­tion ist, müs­sen wir aller­dings sicher­stel­len, dass jede Auf­nahme abge­schlos­sen ist bevor die nächste beginnt. In unse­rem Bei­spiel habe ich ein­fach einen try/catch Block um den Auf­ruf gesetzt um dar­aus resul­tie­rende Feh­ler zu vermeiden.

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

Die ent­schei­dende Zeile ist der Auf­ruf von camera.CaptureImage(). Sie kön­nen ja pro­bie­ren, was pas­siert, wenn Sie nur diese Zeile ver­wen­den und die But­ton öfter hin­ter­ein­an­der drü­cken. Die Anwen­dung wird abstürzen.

Mit der Cap­tureIm­age() Methode sagen wir der Kamera nur, dass sie jetzt ein Bild auf­neh­men soll. Wenn wir kei­nen Event Hand­ler erstel­len, der das Resul­tat emp­fängt, wer­den wir das Ergeb­nis nie bekom­men. Hierzu ver­wen­den wir den Cap­tureIm­age­Avail­able Event unse­res Pho­to­Ca­mera Objekts.

Damit sie den gan­zen Code in Ihre eigene Datei kopie­ren kön­nen, habe ich unten die kom­plette code-behind Datei ein­ge­fügt. Die wich­ti­gen Stel­len sind der neue Event Hand­ler in unse­rer OnNa­vi­ga­tedTo Methode, der camera_CaptureImageAvailable Event Hand­ler und die Methode Thre­ad­SafeIm­age­Cap­ture, wel­che letzt­end­lich das Ergeb­nis unse­rer Auf­nahme emp­fängt. Beach­ten Sie auch, dass wir alle Event Hand­ler in der Methode OnNa­vi­ga­ted­From wie­der von den ent­spre­chen­den Ereig­nis­sen lösen. Damit spa­ren wir Spei­cher und Akku­lauf­zeit, wenn die Anwen­dung im Hin­ter­grund 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 Thre­ad­SafeIm­age­Cap­ture sehen, erstel­len wir ein neues Bit­ma­pI­mage Objekt (wel­ches ein using System.Windows.Media.Imaging State­ment ganz oben benö­tigt) und set­zen die Source die­ses Bit­ma­pI­mage auf das Ergeb­nis unse­rer Aufnahme.

Die rest­li­chen Schritte in der Methode erset­zen den VideoBrush, den wir zur Anzeige der Roh­da­ten ver­wen­det haben, um das gerade auf­ge­nom­mene Stand­bild anzuzeigen.

Das ist schon mal ein guter Anfang für eine Kame­ra­an­wen­dung. Es gibt aber noch wesent­lich mehr zu wis­sen rund um die Kamera, wie zum Bei­spiel Ver­wen­dung des Hard­ware But­tons der Kamera (den jedes Win­dows Phone ent­hält) oder wie man ein auf­ge­nom­me­nes Bild in den Eige­nen Auf­nah­men ablegt — also an der Stelle, an der auch die nor­male Kamera App ihre Fotos speichert.

Den Hard­ware But­ton der Kamera benutzen

Der Kamera Hard­ware But­ton feu­ert drei ver­schie­dene Events: Shut­ter­Key­Half­Press, Shut­ter­Key­Pres­sed und Shut­ter­Key­Re­leased. Wenn wir unsere eigene Kame­ra­an­wen­dung bauen, hat der But­ton kein Stan­dard­ver­hal­ten — das müs­sen wir sel­ber bauen. Da die Anwen­der höchst­wahr­schein­lich ver­traut sind mit dem Ver­hal­ten des Kame­ra­but­tons sollte sich unsere Anwen­dung bei den drei Events erwar­tungs­ge­mäß verhalten:

  • Shut­ter­Key­Half­Press — durch diese Aktion fokus­siert die Kamera.
  • Shut­ter­Key­Press — durch diese Aktion sollte ein Bild auf­ge­nom­men werden.
  • Shut­ter­Key­Re­leased — durch die­ses Ereig­nis wis­sen wir, dass nichts mehr zu tun ist. Ins­be­son­dere kön­nen wir das Fokus­sie­ren einstellen.

Als ers­tes wer­den wir die drei Ereig­nisse imple­men­tie­ren. Im fol­gen­den Code­bei­spiel fin­det sich nur die Imple­men­tie­rung von OnNa­vi­ga­tedTo — die Event Hand­ler für die ein­zel­nen Ereig­nisse 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 Hand­ler Methode bauen wir die für den Shut­ter­Key­Half­Pres­sed Event, da sie die auf­wän­digste ist (obwohl sie nicht wirk­lich auf­wän­dig ist). Zunächst über­prü­fen wir, dass unser Pho­to­Ca­mera Objekt vor­han­den ist, dann ver­su­chen wir, die Kamera zu fokus­sie­ren. Ich habe den Auf­ruf in einen try/catch Block ein­ge­schlos­sen, da wir Focus() nicht auf­ru­fen kön­nen, wenn die Kamera gerade eine Auf­nahme macht. Eine even­tu­ell auf­tre­tende Excep­tion igno­rie­ren wir ein­fach. Die Excep­tion wird jedes Mal flie­gen, wenn Sie eine Auf­nahme machen, da der But­ton auf dem Weg zum „Pres­sed“ Zustand zwei Mal den „Half-Pressed“ Zustand durch­läuft. Ein­mal auf dem Weg „nach unten“ und ein­mal, wenn er wie­der los­ge­las­sen wird. Wenn Sie die­sen Feh­ler in eine Log­da­tei schrei­ben wol­len, nur zu. Für die­ses Bei­spiel habe ich den catch-Block ein­fach leer gelassen.

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

Für den Shut­ter­Key­Pres­sed Event ver­wende ich den Code von vor­hin, als ich den But­ton zur Auf­nahme eines Bil­des 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 Shut­ter­Key­Re­leased Event. In unse­rem Fall müs­sen wir nur dafür sor­gen, dass wir nicht wei­ter fokus­sie­ren. Can­cel­Fo­cus() ist die Methode, mit der die Kamera das Fokus­sie­ren abbricht.

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

Zum Schluss stel­len wir noch sicher, dass die Event Hand­ler wie­der ent­fernt wer­den, wenn wir die Seite ver­las­sen. Wie schon beim Cap­tureIm­age­Avail­able Event machen wir das in der Methode OnNa­vi­ga­ting­From. Ich habe hier den kom­plet­ten Code der Methode dar­ge­stellt. Neu sind aber nur die unte­ren 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;
   }
} 

Bil­der in Eigene Auf­nah­men abspeichern

Hier­für hat Micro­soft eine Media­Li­brary Klasse erstellt. Wir wer­den spä­ter in die­ser Serie eine ganze Anwen­dung zur Ver­wen­dung der Media­Li­brary bauen — jetzt wol­len wir sie nur benut­zen, um unsere Auf­nahme in den Eige­nen Auf­nah­men abzu­le­gen. Dadurch kann der Anwen­der die Fotos unse­rer Anwen­dung genau so ver­wal­ten wie alle ande­ren Fotos auf dem Telefon.

Der gesamte Pro­zess erfolgt in genau zwei Zei­len Code, wobei die erste Zeile nur darin besteht, eine Refe­renz auf die Media­Li­brary zu erhal­ten. Ganz oben in der Seite, dort wo wir auch das Pho­to­Ca­mera Objekt erstellt haben, fügen wir diese Zeile hinzu.

MediaLibrary library = new MediaLibrary(); 

Das war der erste Schritt. Im zwei­ten Schritt schauen wir uns noch mal die Methode Thre­ad­SafeIm­age­Cap­ture an. Im bis­he­ri­gen Code haben wir die Auf­nahme gemacht, die Eigen­schaft Fill von Rec­tangle geän­dert und sie auf unsere Auf­nahme gesetzt. Für die­ses Bei­spiel enfer­nen wir den gesam­ten Metho­den­rumpf und erset­zen ihn mit:

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

Auf einem ech­ten Win­dows Phone soll­ten Sie jetzt in der Lage sein, ein Foto auf­zu­neh­men. Die­ses soll­ten Sie im Tele­fon im Bil­der Hub unter Eigene Auf­nah­men wie­der fin­den. Hier ist noch mal ein klei­nes Video um zu illus­trie­ren, worum es geht:

Das war’s im Wesent­li­chen zur Kamera! Es gibt natür­lich viele wei­tere Dinge, die man mit den Roh­da­ten der Kamera anstel­len kann, z.B. Videos auf­neh­men, Mani­pu­la­tion des auf­ge­nom­me­nen Fotos, Ände­rung der Blitz-Einstellungen usw. Es gibt eine sehr gute Tuto­ri­al­se­rie bei der Code Sam­ples for Win­dows Phone Seite auf MSDN und Sie fin­den einige Bei­spiele hier.

Zusam­men­fas­sung

Im wahr­schein­lich längs­ten Arti­kel der Serie haben Sie gelernt, wie man die Roh­da­ten der Kamera anzeigt, wie man eine Benut­zer­ober­flä­che zur Auf­nahme eines Bil­des erstellt, wie man den Kamera Hard­ware But­ton dazu ver­wen­den kann und wie man Fotos in Eigene Auf­nah­men spei­chern kann.

Wenn Sie eine kom­plett lauf­fä­hige Win­dows Phone Solu­tion mit dem gan­zen Bei­spiel­code die­ses Arti­kels her­un­ter­la­den möch­ten, kli­cken Sie auf den Down­load Code Button.

Mor­gen wer­den wir kom­plett die Rich­tung ändern und uns einen Teil der Nut­zer­da­ten auf dem Tele­fon anse­hen. Genauer gesagt wer­den wir uns mor­gen mit den Kon­tak­ten des Benut­zers befassen.

Bis dahin!

Hinterlasse eine Antwort

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

*

*

Du kannst folgende HTML-Tags benutzen: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>