02 April 2013 Camera, Windows Phone Robert Muehsig

Wer ein Windows Phone sein eigen nennt und .NET Entwickler ist da liegt es nah mal das Windows Phone SDK auszuprobieren. Der Zugriff auf Hardware geht bei “nativen” Anwendungen naturgemäß deutlich einfacher als es selbst bei modernsten Webanwendung im Jahre 2013 möglich wäre, daher hab ich mir die Kamera API unter Windows Phone 8 angeschaut.

In meinem Beispiel möchte ich einfach nur auf die Kamera zugreifen und ein aktuelles Bild aufnehmen und anzeigen – keine Speicherung, keine Videoaufnahme etc.

Variante 1: Camera Capture Task

Die einfachste Möglichkeit ein Bild aufzunehmen ist auf die mitgelieferte Foto-App zu verweisen – dafür gibt es den “Camera Capture Task”.

Vorteil:

Spezielle Bildeinstellungen (ISO…), Kamerablitz, Zoomeinstellungen etc. kann der Nutzer wie gewohnt nutzen und die eigene App benötigt prinzipell keinen direkten Zugriff auf die Kamera. Generell kann man auch davon ausgehen dass die Foto-App sehr gut getestet ist.

Nachteil:

Aufgenommen Bilder werden auch in der “Camera Roll” gespeichert. Hat der Nutzer nun noch eingestellt dass das Bild automatisch zu Skydrive geladen wird, passiert dies ebenfalls. Die eigene Anwendung bekommt eine Kopie des Bildes. Wenn man also nur temporär das Bild benötigt, die Bilddaten nur innerhalb der Anwendung behalten möchte oder eine eigene (vll. bessere) UI bauen möchte, für den ist diese Variante eher nicht geeignet.

Code:

image

Das Bild selbst zeige ich hinterher im Image Element namens “Result” an. In der Application Bar ist einfach ein Button der zur eigentlichen Camera App weiterleitet.

Der Code ist hierbei denkbar einfach:

CameraCaptureTask definieren und auf Button-Click wird “Show” aufgerufen.

    public partial class MainPage : PhoneApplicationPage
    {
        CameraCaptureTask cameraCaptureTask;

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

            cameraCaptureTask = new CameraCaptureTask();
            cameraCaptureTask.Completed += new EventHandler<PhotoResult>(cameraCaptureTask_Completed);
        }

        private void ApplicationBarIconButton_Click(object sender, EventArgs e)
        {
            cameraCaptureTask.Show();
        }

        private void cameraCaptureTask_Completed(object sender, PhotoResult e)
        {
            if(e.TaskResult == TaskResult.OK)
            {
                BitmapImage image = new BitmapImage();
                image.SetSource(e.ChosenPhoto);
                Result.Source = image;
            }
        }


        private void ApplicationBarIconButtonApi_Click(object sender, EventArgs e)
        {
            NavigationService.Navigate(new Uri("/CameraPreview.xaml", UriKind.Relative));
        }
    }

Bei “Show” wird die Camera App gestartet:

image

Nachdem man das Foto gemacht hat kommt man in das “Complete”-Event. Wobei man aber hier noch prüfen muss ob wirklich ein Bild gemacht wurde oder nicht.

Fertig.

Variante 2: Camera API

Die Camera API ist wesentlich mächtiger – erfordert allerdings auch mehr Aufwand. In meinem Demo zeig ich nur das aktuelle Kamerabild an und kann dann ein Bild knipsen.

Vorteil:

Komplette Kontrolle, die Bilder bleiben in der App (bzw. kann ich es selbst entscheiden) und man kann die wildesten Sachen machen (vermutlich ;))

Nachteil:

Man muss alles selber machen – Blitz, Kameraeinstellungen (wenn man die braucht), Preview-Bild darstellen etc. und die Anwendung braucht den Zugriff auf die Kamera:

image

Code:

Der Aufbau ist fast identisch – nur das ein Canvas-Element benutze um darin das Kamerabild anzuzeigen:

        ...<Grid x:Name="ContentPanel" Grid.Row="1">
            <Canvas x:Name="VideoCanvas">
                <Canvas.Background>
                    <VideoBrush x:Name="videoBrush">
                        <VideoBrush.RelativeTransform>
                            <CompositeTransform x:Name="previewTransform"
                            CenterX=".5"
                            CenterY=".5" />
                        </VideoBrush.RelativeTransform>
                    </VideoBrush>
                </Canvas.Background>
            </Canvas>
            <Image Visibility="Collapsed" x:Name="Result" />
        </Grid>...

Etwas sonderbar, aber ich hab es nicht anders hin bekommen: Das Bild über die API ist immer im Panorama-Mode – wenn ich es im Portrait-Mode anzeigen möchte muss ich das Bild drehen, daher die Transform.

    public partial class CameraPreview : PhoneApplicationPage
    {
        private PhotoCamera cam;

        public CameraPreview()
        {
            InitializeComponent();
        }

        protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
        {
            VideoCanvas.Visibility = Visibility.Visible;
            Result.Visibility = Visibility.Collapsed;
            if (PhotoCamera.IsCameraTypeSupported(CameraType.Primary))
            {
                cam = new PhotoCamera(CameraType.Primary);
                cam.CaptureImageAvailable += new EventHandler<Microsoft.Devices.ContentReadyEventArgs>(cam_CaptureCompleted);
                videoBrush.SetSource(cam);
                previewTransform.Rotation = cam.Orientation;
            }
        }

        private void cam_CaptureCompleted(object sender, ContentReadyEventArgs e)
        {
            this.Dispatcher.BeginInvoke(() =>
                                            {
                                                BitmapImage tempImage = new BitmapImage();
                                                tempImage.SetSource(RotateStream(e.ImageStream, Convert.ToInt32(cam.Orientation)));

                                                Result.Source = tempImage;
                                                Result.Visibility = Visibility.Visible;
                                                VideoCanvas.Visibility = Visibility.Collapsed;
                                            });

        }

        /// <summary>
        /// Picture needs to be rotated - dunno why (because of the ImageRotateTransform... sucks...)
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="angle"></param>
        /// <returns></returns>
        private Stream RotateStream(Stream stream, int angle)
        {
          ...
        }

        protected override void OnNavigatingFrom(System.Windows.Navigation.NavigatingCancelEventArgs e)
        {
            if (cam != null)
            {
                cam.Dispose();

                cam.CaptureImageAvailable -= cam_CaptureCompleted;
            }
        }

        private void like_Click(object sender, EventArgs e)
        {
            cam.CaptureImage();
        }

    }

Beim Navigieren zu dieser Page prüfen wir ob das Phone überhaupt eine Kamera besitzt und initialisieren  das “PhotoCamera” Objekt und setzen einen Event-Handler. Dann muss ich noch das Camerabild richtig drehen (previewTransform.Rotation = cam.Orientation), ansonsten passiert dies:

wp_ss_20130402_0003

Irgendwie… schräg – daher haben wir auch die Rotation drin.

Wenn ich auf den Button drücke wird “CaptureImage” aufgerufen und es geht in den Event-Handler rein. Hierbei passiert fast dasselbe wie bei der ersten Variante, aber durch den Trick mit dem drehen ist jetzt auch das Bild gedreht (keine Ahnung warum… vielleicht kann mir ein XAML/Windows Phone Experte einen Tipp geben was ich falsch mache) und muss entweder “optisch” zurückgedreht werden oder wir drehen das gesamte “File”. Da ich auf Byte/Pixtel/Stream-Niveau eine ziemliche Niete bin und ich den Blogpost von Tim Heuer gefunden hab geb ich einfach den Source Code der Methode wieder ohne es zu kommentieren ;)

/// <summary>
        /// Picture needs to be rotated - dunno why (because of the ImageRotateTransform... sucks...)
        /// Origin: http://timheuer.com/blog/archive/2010/09/23/working-with-pictures-in-camera-tasks-in-windows-phone-7-orientation-rotation.aspx
        /// </summary>
        private Stream RotateStream(Stream stream, int angle)
        {
            stream.Position = 0;
            if (angle % 90 != 0 || angle < 0) throw new ArgumentException();
            if (angle % 360 == 0) return stream;

            BitmapImage bitmap = new BitmapImage();
            bitmap.SetSource(stream);
            WriteableBitmap wbSource = new WriteableBitmap(bitmap);

            WriteableBitmap wbTarget = null;
            if (angle % 180 == 0)
            {
                wbTarget = new WriteableBitmap(wbSource.PixelWidth, wbSource.PixelHeight);
            }
            else
            {
                wbTarget = new WriteableBitmap(wbSource.PixelHeight, wbSource.PixelWidth);
            }

            for (int x = 0; x < wbSource.PixelWidth; x++)
            {
                for (int y = 0; y < wbSource.PixelHeight; y++)
                {
                    switch (angle % 360)
                    {
                        case 90:
                            wbTarget.Pixels[(wbSource.PixelHeight - y - 1) + x * wbTarget.PixelWidth] = wbSource.Pixels[x + y * wbSource.PixelWidth];
                            break;
                        case 180:
                            wbTarget.Pixels[(wbSource.PixelWidth - x - 1) + (wbSource.PixelHeight - y - 1) * wbSource.PixelWidth] = wbSource.Pixels[x + y * wbSource.PixelWidth];
                            break;
                        case 270:
                            wbTarget.Pixels[y + (wbSource.PixelWidth - x - 1) * wbTarget.PixelWidth] = wbSource.Pixels[x + y * wbSource.PixelWidth];
                            break;
                    }
                }
            }
            MemoryStream targetStream = new MemoryStream();
            wbTarget.SaveJpeg(targetStream, wbTarget.PixelWidth, wbTarget.PixelHeight, 0, 100);
            return targetStream;
        }

Fazit:

Am “einfachsten” ist es vermutlich den Task zu nehmen – wenn der Anwendungsfall es zulässt. Die Sache mit der Rotation bei der Camera API ist mir etwas schleierhaft – vielleicht mach ich da auch etwas grundsätzlich falsch. Allerdings dauert es auch einige Sekunden bis man Pixel für Pixel den Stream umgebaut hat – daher würde ich das produktiv so nur “ungern” nehmen. Irgendwer eine Idee? ;)

Weiterführende Links und Beispiele:

Auf der Suche nach ein paar guten Beispielen und weiteren Informationen bin ich auf diese Links gestossen, wobei nicht alles geklappt hat – aber vielleicht nützt es dem einen oder anderen etwas:

EXIF Daten auslesen:

Understanding and Reading Exif Data

Reading and displaying EXIF photo data on Windows Phone (konnte ich nur teilweise auslesen…)

Read Exif data from Image on WP 

Camera:

MSDN: Using Cameras in Your Windows Phone Application

How to create a base camera app for Windows Phone

Image Tips for Windows Phone 7

Handling picture orientation in CameraCaptureTask in Windows Phone 7 (Orientation scheint ziemlich “komplex” zu sein – die Exif Variante ging bei mir nicht)

Rotating an Image in Windows Phone

UI:

PhotoHub - Windows Phone 8 XAML LongListSelector Grid Layout sample

 

Sample Code @ GitHub

 

image


Written by Robert Muehsig

Software Developer - from Saxony, Germany - working on primedocs.io. Microsoft MVP & Web Geek.
Other Projects: KnowYourStack.com | ExpensiveMeeting | EinKofferVollerReisen.de