Apps zur Bildbearbeitung mit dem Nokia Imaging SDK erstellen

Instagram aus Einzelteilen
Kommentare

Seit dem IPO des Bildverfremdungsspezialisten sind mobile Fotoeditoren in höchstem Maße populär: Es gibt kaum eine Plattform, in der nicht dutzende von Bildbearbeitern um die Gunst der Nutzer buhlen. Leider ist die Qualität der Resultate meist bescheiden. Das Nokia Imaging SDK bietet die Möglichkeit, Apps nach den eigenen Wünschen zu entwickeln.

Nokia galt seit jeher als Experte in Sachen Kameras: neben einer jahrelangen Partnerschaft mit Zeiss entwickelte Nokia mit EDOF und PureView zwei hoch interessante „neue Kameratechnologien“. Da die Leistungsfähigkeit moderner Kameras stark durch die Firmware bzw. Betriebssoftware beeinflusst wird, haben die Finnen im Laufe der Zeit enormes Wissen über Bildbearbeitung angesammelt.

Nokia Imaging SDK – Was ist das?

Nokia’s neueste Entwicklerbibliothek enthält eine Vielzahl verschiedener Filter, die Bildmaterial verfremden. Böse Zungen würden die Library als „GIMP für Visual Studio“ bezeichnen – ein Blick auf die in Abbildung 1 gezeigte Funktionsvielfalt zeigt, dass dies nicht unbegründet ist.

Abb. 1: Das Filterpaket kann mit GIMP und Photoshop mithalten (Quelle: Nokia)

In der Anfangszeit stand das Nokia Imaging SDK nur unter Windows Phone 8 zur Verfügung. Da Nokia mittlerweile auch ein „normales“ Windows-8-Gerät anbietet, wurde ein Update erforderlich. Die auch mit Windows 8.1 kompatible Version 1.1 brachte außerdem eine als „Interactive Foreground Segmenter“ bezeichnete Funktion mit, die das Erstellen eines Bluebox-Effekts anhand von vom Benutzer eingegebenen Grenzinformationen ermöglicht.

Der Einsatz des Imaging SDKs erspart Ihnen die Entwicklung eigener Verarbeitungsroutinen. Ein weiterer Vorteil ist, dass es JPGs partiell dekodieren kann. Das bedeutet, dass Bildvorschauen, Zuschnitte und Rotationen mit rasender Geschwindigkeit und unter geringem Speicherverbrauch erfolgen – das Telefon holt sich nur jene Daten aus dem Bild, die es zur Erledigung der anstehenden Aufgabe braucht.

Toolchain für Dummys

Wer Bilder mit GIMP bearbeitet, kennt das Konzept der Ebenen mit Sicherheit. Grafiken entstehen durch die Überlagerung mehrerer halbtransparenter Elemente, die die an sie übergebenen Bilddaten verändern und weitergeben. Nokia bildet dieses System im Imaging SDK nach. Die Grund-Pipeline ist in Abbildung 2 gezeigt.

Abb. 2: Imaging-Applikationen bestehen aus einer Bildquelle, einem oder mehreren Filtern und einer Bildsenke (Quelle: Nokia)

Die Image Source hat die Aufgabe, Bilddaten zu generieren. Normalerweise stammen diese aus einer schon am Telefon befindlichen Bilddatei – dank CameraPreviewImageSource und GradientImageSource können Sie das SDK auch auf Kameradaten oder auf gerenderte Farbverläufe loslassen. Effects sind für die eigentliche Verarbeitung zuständig. Sie exponieren ein Interface vom Typ IImageProvider, weshalb sie für das nachfolgende Element wie eine Image Source aussehen. Die meisten Effekte nehmen einen oder mehrere Parameter entgegen, die die Art der durchzuführenden Transformation beschreiben.

Am Ende der Toolchain findet sich ein Renderer. Er verwandelt die generierten Daten in ein Bitmap, ein JPEG oder ein für XAML-Widgets weiterverarbeitbares WriteableBitmap.

Erste Schritte

Nach diesen einführenden Überlegungen ist es an der Zeit, ein erstes Beispielprogramm zusammenzubauen. Nokia liefert das SDK in Form von NuGet-Paketen aus, weshalb wir mit der Erstellung eines leeren Projekts vom Typ Windows-Phone-App beginnen. Als Zielbetriebssystemversion muss 8.0 ausgewählt werden.

Im nächsten Schritt bedarf die Paketverwaltung eines Updates. Öffnen Sie den unter Tools befindlichen Erweiterungsmanager (Extensions and Updates), und suchen Sie nach dem Eintrag von NuGet. Wenn dieser nicht Version 2.8 aufweist, so müssen Sie ihn aktualisieren. Sollte Ihre Visual-Studio-Instanz die Anzeige des Update-Buttons verweigern, so löschen Sie die installierte Version. Laden Sie danach eine neue herunter – NuGet findet sich unter ONLINE | VISUAL STUDIO GALLERY.

Ein Rechtsklick auf den References-Ordner ihres Projekts öffnet die NuGet-Managementkonsole. Suchen Sie in der Rubrik ONLINE | ALL nach Nokia, und klicken Sie auf den in Abbildung 3 gezeigten Install-Knopf.

Abb. 3: Dieser Button bindet die Bibliotheken in Ihr Projekt ein

Das Nokia Imaging SDK ist mit AnyCPU- und x64-Projekten nicht kompatibel. Aus diesem Grund müssen Sie die Projektkonfiguration bereinigen – die dazu notwendigen Werkzeuge finden Sie unter BUILD | CONFIGURATION MANAGER. Unser erstes Beispiel wird eine konfigurierbare Bildverarbeitungs-Pipeline realisieren. Als Erstes brauchen wir eine Anzeigefläche für unsere Resultate. Diese realisieren wir in Form eines Image-Widgets, die Ausführung der Render-Routine erfolgt durch einen Button. Sein Event Handler hat die Aufgabe, die asynchrone Verarbeitungsfunktion anzuwerfen (Listing 1).

private void CmdGenerate_Click(object sender, RoutedEventArgs e)
{
  runPipeline();
}
Die eigentliche Intelligenz unseres Programms findet sich in runPipeline:
private async void runPipeline()
{
  var rad = new RadialGradient(new Windows.Foundation.Point(0.5, 0.5), new EllipseRadius(0.3, 0.3));
  rad.Stops = new GradientStop[] {
    new GradientStop() { Color = Windows.UI.Color.FromArgb(255, 255, 0, 0), Offset = 0 },
    new GradientStop() { Color = Windows.UI.Color.FromArgb(255, 0, 0, 255), Offset = 1 }
  };
  using (var grad = new GradientImageSource(new Windows.Foundation.Size(436, 266), rad))
  {
    var buffer = await new WriteableBitmapRenderer(grad, myDataSink).RenderAsync();
MyImage.Source=buffer;
  }
}

GradientImageSources generieren Bilder aus dem Nichts. Dazu ist neben Informationen über die Bildgröße auch eine Klasse erforderlich, die die Generationsanweisungen enthält. RadialGradient erstellt einen runden Farbverlauf, dessen geometrische Eigenschaften durch die an den Konstruktor übergebenen Werte beeinflusst werden. Das im zweiten Schritt erstellte GradientStop-Array liefert die zu verwendenden Farbinformationen an.

Damit sind wir – fürs Erste – fertig. Die GradiantImageSource wird an den Renderer übergeben, der seine Arbeit asynchron ausführt. Nach der erfolgreichen Abarbeitung von RenderAsync aktualisieren wir die Source-Eigenschaft des Widgets. WriteableBitmapRenderer setzen ein initialisiertes WriteableBitmap voraus. Wir erledigen dies im Rahmen der Erstellung der Klasse, indem wir ein korrespondierendes Member hinzufügen:

public partial class MainPage : PhoneApplicationPage
{
  WriteableBitmap myDataSink = new WriteableBitmap(437,267);

Beachten Sie bitte, dass der WriteableBitmapRenderer auf Größenunterschiede zwischen Bildquelle und Ablage mit einer ArgumentMismatchedException reagiert – dieses Verhalten ist für Quereinsteiger verwirrend.

Wenn Sie dieses Programm im Simulator oder auf einem realen Endgerät ausführen, so erhalten Sie das in Abbildung 4 gezeigte Resultat. Das genaue Verhalten des Gradienten lässt sich sowohl durch das übergebene Array als auch durch die Wahl eines anderen Gradiententyps beeinflussen.

Abb. 4: Unsere Image Source beliefert das Image-Steuerelement mit Informationen

Aufmacherbild: vintage photo of Blank picture frame paper on wooden table von Shutterstock / Urheberrecht: Aneta_Gu

[ header = Seite 2: Filter herbei ]

Filter herbei

Das Erstellen von mehrfarbigen Gradienten ist mit Sicherheit interessant, zeigt aber nur einen Bruchteil der Möglichkeiten des SDKs auf. Im nächsten Schritt wollen wir einen Filter hinzufügen, der das Bild weiter verfremdet. Dazu müssen wir runPipeline anpassen. Die neue Version der Methode ist in Listing 2 zu sehen.

private async void runPipeline()
{
  var rad = new RadialGradient(new Windows.Foundation.Point(0.5, 0.5), new EllipseRadius(0.3, 0.3));
  rad.Stops = new GradientStop[] {
    new GradientStop() { Color = Windows.UI.Color.FromArgb(255, 255, 0, 0), Offset = 0 },
    new GradientStop() { Color = Windows.UI.Color.FromArgb(255, 0, 0, 255), Offset = 1 }
  };
  using (var grad = new GradientImageSource(new Windows.Foundation.Size(436, 266), rad))
  {
    FilterEffect myFilterEffectCollector=new FilterEffect(grad);
    var aFilter = new CartoonFilter();
    myFilterEffectCollector.Filters = new IFilter[] { aFilter };
    var buffer = await new WriteableBitmapRenderer(myFilterEffectCollector, myDataSink).RenderAsync();
    MyImage.Source=buffer;
  }
}

FilterEffect dient als Sammelpunkt für die anzuwendenden Filter, die Klasse enthält außerdem eine Referenz auf das als Ausgangspunkt dienende Urbild. Wir erstellen eine Instanz von CartoonFilter, und schreiben diese in das Filters-Array von myFilterEffectCollector. Die eigentliche Erzeugung der Bilddaten erfolgt wieder im Renderer. Er bekommt diesmal einen Verweis auf die Filtersammlung – da sie das ImageProvider-Interface realisiert, ändert sich im Rest des Codes nichts. Führen Sie dieses Programm aus, um das in Abbildung 5 gezeigte Bild zu erhalten.

Abb. 5: Der „CartoonFilter“ reduziert die Anzahl der im Bild enthaltenen Farben

…und noch einen!

Es gibt kaum ein Bild, dessen Weiterverarbeitung nicht mit Beschneiden (crop) und Drehen (rotate) beginnt. Beide Funktionen sind im Nokia Imaging SDK über eine als Reframing Filter bezeichnete Komponente realisiert, die wir an dieser Stelle kurz ansehen wollen. Aus Platzgründen drucken wir diesmal nur den inneren Teil der Pipeline-Funktion ab (Listing 3).

using (var grad = new GradientImageSource(new Windows.Foundation.Size(436, 266), rad))
{
  FilterEffect myFilterEffectCollector=new FilterEffect(grad);
  var aFilter = new CartoonFilter();
  var bFilter = new ReframingFilter();
  bFilter.ReframingArea = new Windows.Foundation.Rect(30, 30, 436 - 60, 266 - 60);
  bFilter.Angle = 40;
  myFilterEffectCollector.Filters = new IFilter[] { aFilter, bFilter };
  var buffer = await new WriteableBitmapRenderer(myFilterEffectCollector, myDataSink).RenderAsync();
  MyImage.Source=buffer;
}

Ein Reframing Filter beschneidet und rotiert Bilder en bloc. Wir übergeben hier den Zielbereich und den gewünschten Grad der Drehung – der festlegbare Mittelpunkt liegt wie von Haus aus vorgegeben in der Mitte des Bilds. Die Aktivierung erfolgt durch das Einschreiben in das Filters-Array. Aufgrund der geringen Größe unseres Bilds erfolgt die Verarbeitung unserer Filterkette sogar im Emulator ohne nennenswerte Verzögerungen. Komplexere Filterketten können einige Zeit in Anspruch nehmen – es ist sinnvoll, das Stichwort async auch wirklich ernst zunehmen.

Wenn Sie dieses Programm ausführen, so erscheint das Bild – wie in Abbildung 6 gezeigt – rhomboid verschoben und verzerrt.

Abb. 6: Der Reframing Filter funktioniert

[ header = Seite 3: Hauseigene Effekte ]

Hauseigene Effekte

Version 1.0 verursachte einen Binary Break – mit den Betaversionen zusammengebaute Applikationen mussten adaptiert werden, um mit neueren Versionen der Bibliothek kompatibel zu sein. Dieser für Entwickler unerfreuliche Zustand war unvermeidbar, da die Finnen die Erstellung hausgemachter Effekte nachrüsten wollten. Eigene Bildquellen entstehen in Klassen, die CustomImageSourceBase realisieren. Diese abstrakte Klasse schreibt nur eine einzige Methode vor:

class TestImageSource : CustomImageSourceBase
{
  protected override void OnProcess(PixelRegion pixelRegion)
  {
    throw new NotImplementedException();
  }
}

OnProcess hat die Aufgabe, die als Parameter übergebene Pixelregion mit Farbinformationen zu befüllen. Die Breite und die Höhe der dort abgelegten Daten obliegen Ihnen: Es ist in der Praxis üblich, eine Konstruktorfunktion anzulegen, die diese Informationen im Rahmen der Erstellung der Klasse abfragt. Hauseigene Effekte lassen sich nach einem ähnlichen Schema erstellen (Listing 4).

public class MyEffect : CustomEffectBase
{
  protected override void OnProcess(PixelRegion sourcePixelRegion, PixelRegion targetPixelRegion)
  {
    sourcePixelRegion.ForEachRow((index, width, pos) =>
      {
        for (int x = 0; x < width; ++x, ++index)
        {
          uint color = sourcePixelRegion.ImagePixels[index];
          var a = (byte)((color >> 24) & 255);
          var r = (byte)((color >> 16) & 255);
          var g = (byte)((color >> 8) & 255);
          var b = (byte)((color) & 255);
          r = r;
          g = 0;
          b = 0;
          var newColor = (uint)(b | (g << 8) | (r << 16) | (a << 24));
          targetPixelRegion.ImagePixels[index] = newColor;
        }
    });
  }
}

OnProcess bekommt diesmal zwei Parameter überreicht. Die sourcePixelRegion enthält die zu verarbeitenden Daten, die in die targetPixelRegion weitergeschrieben werden müssen. In diesem Beispiel nutzen wir die ForEachRow-Methode, die jede Zeile des Bilds mit einem Delegaten bearbeitet. Unser Code beschränkt sich darauf, die angelieferten Bilddaten von ihren Grün- und Blaukomponenten zu befreien. Danach wandern diese in die Zielregion, wo sie für andere Effekte bereitstehen. Die Bildquelle wird im Rahmen der Erstellung des Filters eingepflegt. Aus diesem Grund müssen wir unsere Klasse noch um einen kleinen Konstruktor erweitern, der die eingehenden Informationen an die Basisklasse weitergibt:

public MyEffect(IImageProvider source) : base(source)
{
}

Hauseigene Effekte lassen sich nicht in ein FilterEffect-Array einschreiben, da sie das IFilter-Interface nicht implementieren. Sie wandern stattdessen direkt in die Rendering-Pipeline – diese Vorgehensweise ist in Listing 5 beschrieben.

using (var grad = new GradientImageSource(new Windows.Foundation.Size(436, 266), rad))
{
  FilterEffect myFilterEffectCollector=new FilterEffect(grad);
  var aFilter = new CartoonFilter();
  var bFilter = new ReframingFilter();
  bFilter.ReframingArea = new Windows.Foundation.Rect(30, 30, 436 - 60, 266 - 60);
  bFilter.Angle = 40;
  myFilterEffectCollector.Filters = new IFilter[] { aFilter, bFilter};
  MyEffect cEffect = new MyEffect(myFilterEffectCollector);

  var buffer = await new WriteableBitmapRenderer(cEffect, myDataSink).RenderAsync();
  MyImage.Source=buffer;
}

Unsere FilterEffect-Instanz ist – grob gesprochen – eine Bildquelle, die die durch sie hindurchmarschierenden Informationen ein wenig verfremdet zurückgibt. Aus diesem Grund spricht nichts dagegen, sie einfach als Quelle für unseren hauseigenen Effekt zu verwenden. Dieser dient daraufhin als Datenquelle für den Renderer.

Während der Programmausführung arbeitet das Image SDK die Pipeline rekursiv ab. Das bedeutet, dass die Gradientenquelle ihren Farbverlauf generiert und an die Filter weitergibt. Diese reichen die Ergebnisse ihrer Arbeit an unseren Custom Effect weiter, der daraufhin zur Versorgung des Renderers schreitet. Das Resultat der Arbeit ist in Abbildung 7 gezeigt.

Abb. 7: Dank des Custom-Filters erscheinen nur Rottöne am Bildschirm

Leider ist im Moment keine Methode zur Erstellung eigener Renderer bekannt. Das ist kein wirkliches Problem: Generieren Sie ein WriteableBitmap, und lesen Sie seine Inhalte danach zwecks Kompression zeilenweise aus.

[ header = Seite 4: Bild in live]

Bild in live

Heutige Smartphones sind so schnell, dass sie kleinere Effekt-Pipelines in Echtzeit abarbeiten können. Dies lässt sich zur Erzeugung einer Livevorschau missbrauchen. Zur Steigerung der Performance ist es wichtig, die Aktualisierung des Anzeigeelements nicht über die Source-Eigenschaft vorzunehmen. Stattdessen sollten Sie die VideoBrush-Eigenschaft einsetzen, die die Bilddaten weitaus schneller in den Grafikspeicher schiebt:


  
     
  

Im nächsten Schritt müssen Sie eine hauseigene Effektquelle erstellen und diese an den BackgroundVideoBrush anbinden. Weitere Informationen dazu finden Sie in einem kompletten Codebeispiel von Nokia.

Achtung, Samsung

Windows Mobile war immer für seine enorme Marktbreite bekannt: Es gab kaum einen Hersteller, der das Betriebssystem nicht auf einigen seiner Telefone einsetzte. Nach der Einführung von Windows Phone änderte sich dies radikal: bisher sind nur Nokia, HTC, Samsung und Huawei mit dabei.

Aufgrund des enorm starken Marketings der Finnen haben sich die Marktanteile von Samsung und HTC mittlerweile atomisiert – mehr als 95 Prozent der Nutzerschaft arbeiten mit einem Telefon von Nokia.

Wir testeten unser Beispiel trotzdem auch auf einem unter Windows Phone 8 laufenden Samsung Ativ S. Das Nokia Imaging SDK funktionierte am „Fremdgerät“ ohne jegliche Probleme – die Verwendung der APIs schränkt die Reichweite ihres Programms nicht ein.

Sprechen Sie C++?

Bildbearbeitungsroutinen entstehen normalerweise in C++ und/oder Assembler. Dank WinRT lässt sich Nokia’s Code ohne Probleme aus Visual Basic und C# heraus ansprechen. Da WinRT zwischen den Sprachen keine nennenswerten Unterschiede macht, gilt dies auch für in C++ gehaltene Applikationen für Windows 8.

Die wenigsten Windows-Phone-Entwickler arbeiten mit C++. Aus diesem Grund bietet Nokia hier weder Beispielcode noch Dokumentation an. Stattdessen empfehlen die Finnen die Verwendung der .winmd-Dateien – die Translation der C#-Syntax in C++ erfolgt sodann im Kopf. Aufgrund der hohen Effizienz von C# sind die durch den Einsatz von C++ erreichbaren Performancesteigerungen vergleichsweise gering. Der einzige wirkliche Vorteil tritt bei der Verarbeitung von aus der Kamera stammenden Bildern auf – die Eingangskonversion in JPG entfällt hier ersatzlos.

Mehr erfahren

Es ist aufgrund des enormen Umfangs der Bibliothek schlicht unmöglich, sie in einem einzelnen Fachartikel abzuhandeln. Für Entwickler, die an Konzepten und Vorgehensweisen interessiert sind, gibt es eine Gruppe von Artikeln, die die Verwendung des Nokia Image SDK anhand kleiner Beispiele erklärt. Genauere Informationen zu einzelnen Klassen finden sich dort nicht. Nokia hat die API-Referenz in einen eigenen Hub ausgelagert und eine Übersicht mit Beispielen zusammengestellt.

Zu guter Letzt bieten die Finnen immer wieder Webinare an, die die Verwendung des Imaging SDK anhand anschaulicher Beispiele vorführen.

Fazit

Das Nokia Imaging SDK stellt eine Vielzahl von attraktiv aussehenden Effekten bereit, deren manuelle Implementierung in Arbeit ausarten würde. Wenn Ihr Programm von Haus aus Bilder bearbeitet, können Sie durch die Einbindung der von Nokia angebotenen Routinen Zeit sparen.

Die Resultate der Algorithmen sind dank der langjährigen Erfahrung der Finnen im Bereich Bildverarbeitung von mehr als solider Qualität. Zudem gibt es in DVLUP immer wieder Aufgaben, die sich durch eine „maßgeschneiderte Wegwerf-App“ ohne großen Aufwand realisieren lassen. Die dadurch generierten Punkte lassen sich sodann in Marketingpunkte für andere, wichtigere Applikationen umsetzen.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -