Schnipp und Weg. Foto mit Windows 8

Als Vorbereitung für das nächste VB.net WinRT Beispiel, wollte ich ein Foto machen. Was unter Silverlight, quasi ein drei Zeiler ist, erweist sich mit WinRT als ziemlich kompliziert. Die System.IO Klassen sind stark verändert. Streams funktionieren ganz anders und es gibt kein einfache Möglichkeit ala WriteableBitmap den Inhalt des Videos zu rendern. Sogar die Anzeige geht ganz anders über ein XAMLCaptureElement Control. Zunächst der Screen mit Start Button, einem ToggleSwitch für die vordere oder Rückwärtige Kamera. Ein Slider für die Helligkeitsregelung und ein Knopf für den Datei Upload.

Screenshot (5)

Dazu noch der XAML Code um im nachfolgenden Visual Basic Beispiel die Namen der Steuerelemente zu finden.

 <Button Content="Button" HorizontalAlignment="Left" Margin="523,-2,-198,0" VerticalAlignment="Top" 
Width="75" Click="Button_Click_1"/> <ToggleSwitch x:Name="toggle1" Header="Kamera" Visibility="Collapsed" OffContent="Back" OnContent="Front" HorizontalAlignment="Left" Margin="532,42,-286,0" VerticalAlignment="Top"/> <CaptureElement x:Name="Capture1" HorizontalAlignment="Left" Height="358" Width="490" Margin="10,10,-100,-68" VerticalAlignment="Top"/> <Slider x:Name="sldHell1" HorizontalAlignment="Left" Margin="523,105,-223,0" VerticalAlignment="Top" Width="100"/> <Button Content="upload" HorizontalAlignment="Left" Margin="523,179,-198,0" VerticalAlignment="Top"
Width="75" Click="Button_Click_2"/> <Image x:Name="img1" HorizontalAlignment="Left" Height="111" Margin="523,236,-262,-47"
VerticalAlignment="Top" Width="139"/>

 

Beim starten der METRO App werden die verfügbaren Kameras aufgelistet. In der Regel haben Windows 8 Tablet Computer eine Front und eine Back Kamera.

Private mc As MediaCapture
Di
m cams As DeviceInformationCollection
Private Async Sub UserControl17_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded cams = Await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture) toggle1.Visibility = Windows.UI.Xaml.Visibility.Visible End Sub

Die Kamera muss gestartet werden, dazu gibt es den Button und das asynchrone Event zum start der Kamera. In appxmanifest muss die Capability Webcam aktiviert werden Der Benutzer erhält als zusätzliche Sicherheitsschranke beim Start noch einen Genehmigungsdialog.

Mit dem ToggleButton wird gesteuert welche Webcam angezeigt werden soll.

Da ich dieses WinRT Code Beispiel abends geschrieben habe, war mein Bild schwarz. Erst dachte ich mein Code ist fehlerhaft. Dann habe ich zum debuggen mit der Taschenlampe gegriffen und einfach in die Kamera geleuchtet.  Die Settings waren einfach nur zu dunkel. Ganz neue Fehlersucherfahrungen. Jedenfalls habe ich dann noch einen Slider eingebaut um die Helligkeit zu regeln.

Die Grenzwerte sind offensichtlich nicht fix, sondern müssen erst aus dem Video Device ausgelesen werden.

Private Async Function Button_Click_1(sender As Object, e As RoutedEventArgs) As Task
  Try
     Dim mcs = New MediaCaptureInitializationSettings()
     mcs.VideoDeviceId = cams(CInt(toggle1.IsOn) + 1).Id
     mc = New MediaCapture()
     Await mc.InitializeAsync(mcs)
     Capture1.Source = mc
     Await mc.StartPreviewAsync()
     sldHell1.Maximum = mc.VideoDeviceController.Brightness.Capabilities.Max
     sldHell1.Minimum = mc.VideoDeviceController.Brightness.Capabilities.Min
     sldHell1.StepFrequency = mc.VideoDeviceController.Brightness.Capabilities.Step
  Catch ex As Exception
     Dim msg As MessageDialog = New MessageDialog(ex.Message)
     msg.ShowAsync().AsTask()
  End Try
End Function

Die Helligkeit regelt man dann, im Slider Event. Der Weg über TrygetValue muss vermutlich genommen werden, weil wir genau an dieser Stelle die Grenze zwischen dem .NET Profil und der WinRT überschreiten (Projection genannt). Intuitiv ist was anderes.

Private Sub sldHell1_ValueChanged(sender As Object, e As RangeBaseValueChangedEventArgs) 
Handles sldHell1.ValueChanged Dim b As Boolean = mc.VideoDeviceController.Brightness.TrySetValue(sldHell1.Value) End Sub

 

Wenn wir schon bei Intuitiv sind, die folgende Zeilen sind ziemlich aufwändig. Das Beispiel macht das Bild von der Kamera (Screenshot) und lädt es auf einen Webserver hoch. Zum Vergleich ein Silverlight Artikel der WriteableBitmap verwendet.

Den Namen des Bildes liefere ich als Querystring mit. Der Upload wird von einem ASP.NET Handler (ashx) am Web Server IIS entgegen genommen.

Um das Bild als JOG zu kodieren, kommt die ImageEncodingPropertys Klasse zum Einsatz. Warum man hier als Zeichenkette JPEG übergeben muss und nicht auf eine Auflistung zurück greifen kann, ist mir schleierhaft. Mögliche Werte sind "BMP","ICO","GIF","JPEG","PNG","TIFF","WMP".

Der Nebel wird dichter wenn man sich um die Streams kümmert. Da z.B. Datei Operationen asynchron erfolgen müssen, ist vieles aus System.io gestrichen worden und komplett neu und anders wieder in Windows.Storage.Streams auftauchen. Das Beispiel von InMemoryRandomAccessStream bis zum Bytearray habe ich mir mühsam aus der Doku zusammengesucht und dann von meinem Microsoft TAP Betreuer sogar absegnen lassen, weil ich es optisch misslungen finde. Code reuse aus .NET ist hier Fehlanzeige.

Der letzte Teil behandelt den Upload per HTTP POST, wie ich es hier im Blog schon mehrfach beschrieben habe.

Private Async Function Button_Click_2(sender As Object, e As RoutedEventArgs) As Task
    Dim http As New HttpClient()
     Dim url As New Uri("http://win8-pre9/lab1/upload1.ashx?datei=" & Date.Now.Ticks)
      Dim img As ImageEncodingProperties = New ImageEncodingProperties()

     img.Subtype = "JPEG"
     img.Width = 320
     img.Height = 240

     Dim rs As IRandomAccessStream = New InMemoryRandomAccessStream()
     Await mc.CapturePhotoToStreamAsync(img, rs)

     Dim rd As DataReader = New DataReader(rs.GetInputStreamAt(0))
     Await rd.LoadAsync(rs.Size)
     Dim b As Byte() = New Byte(rs.Size - 1) {}
    rd.ReadBytes(b)
     Dim sc As ByteArrayContent = New ByteArrayContent(b)

     Dim req As HttpRequestMessage = New HttpRequestMessage(HttpMethod.Post, url)
     req.Content = sc
     req.Headers.TransferEncodingChunked = True
     Dim resp As HttpResponseMessage = Await http.SendAsync(req)
     Dim msg As MessageDialog = New MessageDialog(Await resp.Content.ReadAsStringAsync)
     Await msg.ShowAsync()
End Function

Letztendlich noch der ASHX Handler auf dem Web Server. Im IIS braucht der APPpool Schreibrechte, die er z.B auf APP_DATA hat. Hier funktionieren die Streams für Datei Zugriff wie gewohnt.

 Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
        Dim f As String = context.Request.QueryString("datei")
        Using fs As FileStream = File.Create(context.Server.MapPath("~/images/") & f & ".jpg")
            SaveFile(context.Request.InputStream, fs)
        End Using
        context.Response.ContentType = "text/plain"
        context.Response.Write("Erfolg")

End Sub
Private Sub SaveFile(ByVal stream As Stream, ByVal fs As FileStream)
   Dim buffer(4096) As Byte
   Dim bytesRead As Integer
   bytesRead = stream.Read(buffer, 0, buffer.Length)
   Do While bytesRead <> 0
            fs.Write(buffer, 0, bytesRead)
            bytesRead = stream.Read(buffer, 0, buffer.Length)
   Loop
End Sub

Pingbacks and trackbacks (1)+

Kommentar schreiben