Freihand Icons für WPF

07.10.2011

Ich arbeite gerade an einer WPF-Applikation, welche wie ein Notizblock aussehen soll. Natürlich passen die von Windows bereitgestellten Icons und Buttons nicht wirklich zu diesem Design. Also habe ich überlegt, wie ich handgezeichnete Icons in WPF übernehmen kann. Der erste Versuch einfach eingescannte PNG-Bilder zu verwenden sah - zumindest in höheren Zoomstufen - schrecklich aus. Doch zum Glück gibt es eine Möglichkeit Icons als Vektorgrafik in WPF zu überführen.

Vorbereitungen

Zuerst benötigt man ein wenig Software:

  • Inkscape

    Vektorzeichenprogramm welches seit Kurzem auch XAML speichern kann. Ich verwende es zum Vektorisieren der Grafiken.

  • Paint.NET

    Rasterzeichenprogramm welches ich zum Einlesen des Scans und zur Aufbereitung der Vorlage benutzt habe. Hier funktioniert auch jeder andere Grafikeditor. Wer sich jedoch genau an das Tutorial halten will, sollte Paint.NET herunterladen und installieren.

  • Visual Studio 2010

    Eigentlich ist Visual Studio nicht unbedingt notwendig. Ich verwende es jedoch für dieses Beispiel und daher habe ich es hier aufgeführt. Es funktioniert natürlich auch jedes andere IDE mit XAML-Support.

Nachdem die ganze Software installiert ist, geht es erst einmal offline weiter.

Icons entwerfen

Ich habe zuerst auf einem etwas festeren Karton (besserer Kontrast beim Scannen und kein Durchdrücken von Linien) mit Bleistift ein dünnes Raster vorgezeichnet. Das Raster gibt beim Zeichnen einen Anhaltspunkt und sorgt für gleiche Proportionen für alle Icons.

Zuerst sollte man den Entwurf mit Bleistift skizzieren. Vor dem Scannen werden dann die Linien mit einem Fineliner oder Tuschestift nachgezogen, damit diese sich gut abheben.

Anschließend wird erst einmal alles eingescannt. Dafür gibt es in Paint.NET den Menüpunkt “Datei” - “Übernehmen”. Dieser erlaubt es Daten von einem Scanner zu importieren. Das Ergebnis zeigt das folgende Bild. Scan der Icons in Rohform

Aufbereiten des Scans

“Kurven”-Dialog mit Sprungfunktion Auch auf dem besten Scanner wird das eingelesene Bild nicht sofort für die Weiterverarbeitung brauchbar sein. Zuerst muss der Kontrast gesteigert und das Hintergrundraster entfernt werden. Hierfür bietet Paint.NET zum Glück eine elegante Möglichkeit: Nachdem die Zeichnungen (dank Fineliner oder Tusche) deutlich dunkler als die Hilfslinien oder eventuelle Störungen sind lassen sich diese leicht ausfiltern. Hierfür öffnet man den “Kurven”-Dialog über “Korrekturen” - “Kurven…”. Die “Kurven”-Funktion erlaubt es einer Eingangshelligkeit eine bestimmte Ausgangshelligkeit zuzuweisen. D.h. die Helligkeitsinformation der einzelnen Pixel wird durch die Kurve auf neue Helligkeitswerte abgebildet. Da der Kontrast des Bildes maximal erhöht werden soll, habe ich eine Sprungfunktion verwendet. D.h. alle Helligkeitswerte unterhalb einer bestimmten Schwelle werden auf Schwarz und alle Werte darüber auf Weiß abgebildet. Mit der Position und der Steilheit der Kurvenform muss man etwas experimentieren.

Erst wenn das Bild (fast) schwarz-weiß aussieht, geht es weiter. Der nachfolgende “Optimierte Scan” zeigt, wie die Grafiken aussehen sollten, bevor sie mit Inkscape weiterverarbeitet werden können. Optimierter Scan

Vektorisieren

Um die Grafiken schlussendlich in Vektorinformationen zu übersetzen (welche anschließend in XAML genutzt werden können), muss zuerst eine der Zeichnungen ausgeschnitten werden. Hierbei muss man nicht auf das letzte Pixel exakt arbeiten. Nach dem Vektorisieren werden weiße Bereiche entfernt. Als Beispiel habe ich habe mich für einen Zeichenstift entschieden. Dieser wird in Paint.NET markiert, kopiert (Strg+C) und anschließend in Inkscape eingefügt (Strg+V). Bitmap-Grafik in Inkscape Die Bitmapgrafik muss jetzt vektorisiert werden. Das geht über den Menüpunkt “Pfad” - “Bitmap vektorisieren…”. Der Vektorisierungsdialog bietet einige Einstellungen, von denen nur wenige für diesen Zweck relevant sind. Wenn man andere Icons (z.B. in Farbe) vektorisieren möchte, so sollte man mit den Einstellungen etwas herumspielen. Dabei leistet die Vorschau auf der rechten Seite des Einstellungsdialogs gute Dienste. Für meine Schwarz-Weiß-Icons habe ich die “Graustufen”-Einstellung genutzt. Die Menge der Suchdurchgänge sollte man hierbei nicht zu groß wählen, da Inkscape sonst sehr viele Ebenen mit unterschiedlichen Graustufen erstellt. Für mich haben die folgenden Einstellungen gut funktioniert: Einstellungen des Vektorisierers Auf dem zweiten Reiter “Optionen” habe ich zwar keine Einstellungen vorgenommen, da ich aber nicht weiß, inwieweit die Standardwerte gleich bleiben, hier eine Kurze Übersicht der Einstellungen: Einstellungen auf dem Reiter “Optionen”

Anschließend startet man mit einem Klick auf “Ok” die Vektorisierung. Der Dialog verschwindet aber danach nicht und muss über das “X” geschlossen werden. Ich habe beim ersten Mal ungefähr zehn vektorisierte Kopien erzeugt, bevor mir das aufgefallen ist.

Nun schiebt man erst einmal die vektorisierte Kopie von der Vorlage herunter und löscht die Vorlage aus dem Bearbeitungsfenster (Entf). Bitmapvorlage und vektorisierte Kopie Die vektorisierte Kopie besteht jetzt noch aus mehreren Ebenen in unterschiedlichen Graustufen. Da ich jedoch nur eine einzige Ebene (einen Pfad) haben wollte, musste ich diese Trennen. Hierfür klickt man mit der rechten Maustaste auf das Vektorobjekt und wählt im Kontextmenü “Gruppierung aufheben”. Die einzelnen Ebenen lassen sich dann einfach mit der Maus anklicken und auseinander schieben. Unterschiedliche Ebenen Seite einpassen Meist ist die schwarze Ebene nicht unbedingt die beste. Die etwas dickere, dunkelgraue Ebene hat sich - gerade wenn das Icon relativ klein dargestellt wird - oft als besser erwiesen. Welche Ebene man am Ende benutzt ist Geschmackssache. Auf jeden Fall löscht man nun alle bis auf eine Ebene (Entf) und färbt die verbleibende Ebene durch doppelklicken auf die Farbleiste schwarz ein. Jetzt könnte man das ganze schon als XAML-Datei speichern. Doch dann würde das Icon irgendwo in der Pampa auftauchen, da der Ursprungspunkt des Exports die linke, untere Ecke des Zeichenblattes ist. Um dies zu vermeiden, muss das Zeichenblatt noch auf die Größe der Grafik angepasst werden. Dies geschieht am Einfachsten durch selektieren der Grafik und die Auswahl des Menüpunktes “Datei” - “Dokumenteneinstellungen”. Anschließend klappt man die Option “Ändern der Seitengröße auf Inhalt…” auf und wählt dort “Seite in Auswahl einpassen”. Anschließend lässt sich die Grafik über “Datei” - “Speichern unter…” als Dateityp “Microsoft XAML” abspeichern.

Einbinden in XAML

Nun erhält man eine XAML-Datei folgenden Inhalts (ich habe den Output einer einfacheren Grafik genutzt, um das Beispiel kurz zu halten):

<?xml version="1.0" encoding="UTF-8"?>
<Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Name="svg2" Width="10" Height="356">
  <Canvas.Resources/>
	<Canvas Name="layer1">
	  <Canvas>
		<Canvas.RenderTransform>
		  <TransformGroup>
		  <!--g-->
		  <TranslateTransform X="-139.77038" Y="-89.362155"/>
		</TransformGroup>
	  </Canvas.RenderTransform>
	  <Path xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Name="path2996" Fill="#000000" Data="m 144.86526 434.61216 c -0.25947 -20.70258 -0.27379 -24.77728 -0.0993 -28.25 0.4676 -9.30421 -0.72411 -42.24365 -1.73517 -47.96132 -0.62841 -3.55372 -1.08318 -9.85372 -1.0106 -14 0.22408 -12.80188 0.32971 -30.33989 0.20802 -34.53868 -0.0638 -2.2 0.39953 -7.6 1.02953 -12 0.63 -4.4 1.06019 -8.45282 0.95597 -9.00626 -0.10421 -0.55345 -0.65714 -7.52845 -1.22873 -15.5 -1.1062 -15.42733 -1.35521 -17.68739 -2.80146 -25.42587 -0.52064 -2.78581 -0.55322 -6.26717 -0.0749 -8 0.4658 -1.68733 1.0748 -8.91787 1.35334 -16.06787 0.27855 -7.15 0.7603 -14.8 1.07057 -17 0.74087 -5.25328 0.66618 -47.32431 -0.1145 -64.5 -0.33749 -7.425 -0.58912 -15.975 -0.55917 -19 0.17463 -17.64079 0.33842 -21.12155 1.28224 -27.250005 0.9638 -6.258213 1.22187 -6.75 3.54206 -6.75 l 2.50252 0 -0.78419 7.25 c -0.43131 3.987495 -0.69388 10.258335 -0.58349 13.935185 0.1104 3.67684 -0.141 6.89766 -0.55867 7.15736 -0.41766 0.2597 -0.66199 4.45137 -0.54294 9.31482 0.81612 33.34021 0.96444 80.05177 0.26006 81.90442 -1.99696 5.2524 -3.09784 39.3782 -1.43355 44.43822 0.63315 1.925 1.01348 5.75 0.84519 8.5 -0.64719 10.575 -0.53962 14.22376 0.53723 18.2229 0.68838 2.55643 0.89403 8.27246 0.53814 14.95738 -1.43311 26.91945 -1.53547 31.67156 -1.18078 54.81972 0.13483 8.8 0.68888 16.675 1.23121 17.5 0.54233 0.825 1.08922 7.8 1.21531 15.5 0.1261 7.7 0.36953 20.75 0.54097 29 0.17144 8.25 0.11291 19.1625 -0.13008 24.25 -0.24654 5.16189 -0.41226 7.93524 -0.93581 8.95244 -0.30537 0.59328 -0.7533 0.29756 -1.35516 0.29756 -0.62892 0 -0.51137 0.26848 -1.26949 -0.17118 -1.07778 -0.62504 -0.63814 -4.49248 -0.71442 -10.57882 z"/>
	</Canvas>
  </Canvas>
</Canvas>

Da Inkscape versucht, die einzelnen Layereigenschaften zu exportieren, ist der XAML-Output stärker geschachtelt als er eigentlich sein müsste. Das Canvas mit dem Namen “Layer1” und das darin enthaltene Canvas können entfallen. Auch die xmlns- und Name-Angaben sind überflüssig, wenn das Icon als Ressource genutzt wird. Ich habe den XML-Quelltext zuerst einmal in ein neues ResourceDictionary kopiert um ihn dort zu bereinigen. Wichtig ist dabei natürlich, dass der <?xml-Header ebenfalls entfernt wird. Das erste Canvas benötigt dann natürlich noch ein x:Key-Tag, über welches auf die Ressource zugegriffen werden kann. Der endgültige Code sieht dann folgendermaßen aus:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
				xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Canvas x:Key="VLine" Width="10" Height="356">
	<Canvas.Resources/>
	<Canvas.RenderTransform>
	  <TransformGroup>
		<TranslateTransform X="-139.77038" Y="-89.362155"/>
	  </TransformGroup>
	</Canvas.RenderTransform>
	<Path Fill="#000000" Data="m 144.86526 434.61216 c -0.25947 -20.70258 -0.27379 -24.77728 -0.0993 -28.25 0.4676 -9.30421 -0.72411 -42.24365 -1.73517 -47.96132 -0.62841 -3.55372 -1.08318 -9.85372 -1.0106 -14 0.22408 -12.80188 0.32971 -30.33989 0.20802 -34.53868 -0.0638 -2.2 0.39953 -7.6 1.02953 -12 0.63 -4.4 1.06019 -8.45282 0.95597 -9.00626 -0.10421 -0.55345 -0.65714 -7.52845 -1.22873 -15.5 -1.1062 -15.42733 -1.35521 -17.68739 -2.80146 -25.42587 -0.52064 -2.78581 -0.55322 -6.26717 -0.0749 -8 0.4658 -1.68733 1.0748 -8.91787 1.35334 -16.06787 0.27855 -7.15 0.7603 -14.8 1.07057 -17 0.74087 -5.25328 0.66618 -47.32431 -0.1145 -64.5 -0.33749 -7.425 -0.58912 -15.975 -0.55917 -19 0.17463 -17.64079 0.33842 -21.12155 1.28224 -27.250005 0.9638 -6.258213 1.22187 -6.75 3.54206 -6.75 l 2.50252 0 -0.78419 7.25 c -0.43131 3.987495 -0.69388 10.258335 -0.58349 13.935185 0.1104 3.67684 -0.141 6.89766 -0.55867 7.15736 -0.41766 0.2597 -0.66199 4.45137 -0.54294 9.31482 0.81612 33.34021 0.96444 80.05177 0.26006 81.90442 -1.99696 5.2524 -3.09784 39.3782 -1.43355 44.43822 0.63315 1.925 1.01348 5.75 0.84519 8.5 -0.64719 10.575 -0.53962 14.22376 0.53723 18.2229 0.68838 2.55643 0.89403 8.27246 0.53814 14.95738 -1.43311 26.91945 -1.53547 31.67156 -1.18078 54.81972 0.13483 8.8 0.68888 16.675 1.23121 17.5 0.54233 0.825 1.08922 7.8 1.21531 15.5 0.1261 7.7 0.36953 20.75 0.54097 29 0.17144 8.25 0.11291 19.1625 -0.13008 24.25 -0.24654 5.16189 -0.41226 7.93524 -0.93581 8.95244 -0.30537 0.59328 -0.7533 0.29756 -1.35516 0.29756 -0.62892 0 -0.51137 0.26848 -1.26949 -0.17118 -1.07778 -0.62504 -0.63814 -4.49248 -0.71442 -10.57882 z"/>
  </Canvas>
</ResourceDictionary>

In XAML kann das Icon nun beispielsweise als Hintergrund für einen Button genutzt werden. Über das Fill-Attribut des Path-Tags lässt sich die Farbe des Icons nachträglich noch anpassen.

<Button Width="32" Height="32" Style="{StaticResource FlowDocumentButton}">
  <Button.Background>
	<VisualBrush Stretch="Uniform" Visual="{StaticResource SaveIcon}"/>
  </Button.Background>
</Button>