Animierte GIFs aus WPF Animationen
Um WPF Animationen in animierte GIFs umzuwandeln, stellt uns System.Windows.Media.Imaging die Klasse GifBitmapEncoder zur Verfügung. Diese macht unser Leben deutlich einfacher. Alles was wir nun noch machen müssen, ist die Animation Frame um Frame abzufotografieren. Das ist sicherlich keine allzu schöne Lösung, doch da es sich bei den WPF Animationen nicht um framebasierte Überblendungen von Einzelbildern handelt, müssen wir entsprechend des Aufbaus von GIFs ein wenig auf Optimierung und Schönheit zumindest bei der Aufnahme verzichten.
In meinem Beispiel verwende ich einen Viewport mit dem schönen Namen "Illusion", da ich darin eine Animation ablaufen lassen, die ich mir von IanG ausgeliehen habe. Dementsprechend habe ich das dazu passende Storyboard mit dem Namen "IllusionStoryboard" versehen. Das macht es mir einfacher die Fotos zu schießen.
Mein Fenster erweitere ich um ein DependencyProperty. Das macht den Umgang mit den WPF Animationsklassen deutlich leichter, wie man unten sehen wird.
public static readonly DependencyProperty FrameProperty =
DependencyProperty.Register(
"Frame",
typeof(double),
typeof(Window1),
new PropertyMetadata(new PropertyChangedCallback(FrameChangedCallback)));
Im Loaded EventHandler meines Fensters, lege ich verschiedene Member an. Nicht zu vergessen natürlich meinen GifBitmapEncoder. Aber auch die DoubleAnimation, um meine Fotos pünktlich zu schießen (man kann sowas auch ohne DoubleAnimation sehr gut und eventuell besser durchführen, aber wir befinden uns hier im XamlBlog - schon vergessen?).
void Window1_Loaded(object sender, RoutedEventArgs e)
{
DoubleAnimation dbl = new DoubleAnimation(0, numberFrames, new Duration(TimeSpan.FromSeconds(2)));
dbl.AccelerationRatio = 1;
BeginAnimation(Window1.FrameProperty, dbl);
encoder = new GifBitmapEncoder();
}
Da wir im eine PropertyChangedCallback angegeben haben, ist unsere Aufgabe nur noch darauf reduziert, dass wir dort die Fotos schießen und am Ende der Animation, diese Fotos auch abspeichern.
private static void FrameChangedCallback( DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Window1 w = d as Window1;
if (w != null)
{
RenderTargetBitmap rtb = new RenderTargetBitmap((int) w.Illusion.ActualWidth,
(int)w.Illusion.ActualHeight, 96d, 96d, PixelFormats.Pbgra32);
rtb.Render(w.Illusion);
BitmapFrame bF = BitmapFrame.Create(rtb);
w.encoder.Frames.Add(bF);
if ((double)e.NewValue == numberFrames)
{
Image img = new Image();
int i = w.encoder.Frames.Count;
FileStream stream = new FileStream("myAnimation.gif", FileMode.Create);
w.encoder.Save(stream);
stream.Close();
}
}
}
Das Ganze ist recht nett und schnell mal dahinprogrammiert, rein um die Möglichkeiten zu testen. Wenn man das ordentlich machen will, dann sollte man auf die Framerate des erzeugten GIFs eingehen und entsprechend die Animation auf Frames umstellen, damit man die Fotos korrekt schießt. Doch für's erste Ausprobieren soll das hier reichen.