Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for controlling animation speed #69

Merged
merged 3 commits into from
Jan 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 38 additions & 4 deletions WpfAnimatedGif.Demo/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
Expand Down Expand Up @@ -57,21 +59,51 @@
Click="btnOpenUrl_Click"/>
</Grid>

<StackPanel Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3"
<StackPanel Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"
Orientation="Horizontal">
<Label Content="Repeat" />
<RadioButton Margin="5" Content="Default" IsChecked="{Binding UseDefaultRepeatBehavior}" />
<RadioButton Margin="5" Content="Forever" IsChecked="{Binding RepeatForever}" />
<RadioButton Margin="5" Content="Specific count" IsChecked="{Binding UseSpecificRepeatCount}" />
<TextBox Margin="5" Text="{Binding RepeatCount}" Width="20" />
<CheckBox Margin="5" Content="Auto start" IsChecked="{Binding AutoStart}" />
<CheckBox Margin="5" Content="Visibility" IsChecked="{Binding GifVisible}" />
</StackPanel>

<StackPanel Grid.Row="1" Grid.Column="2" HorizontalAlignment="Right" Orientation="Horizontal">
<Button Name="btnClearImage" Content="Clear image" Click="btnClearImage_Click" Margin="5,0,0,5" />
<Button Name="btnGC" Content="GC" Click="btnGC_Click" Margin="5,0,5,5" />
</StackPanel>


<StackPanel Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2"
Orientation="Horizontal">
<CheckBox Margin="5" Content="Auto start" IsChecked="{Binding AutoStart}" />
<CheckBox Margin="5" Content="Visibility" IsChecked="{Binding GifVisible}" />
</StackPanel>

<StackPanel Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2">
<RadioButton IsChecked="{Binding UseDefaultDuration}" Content="Default duration" />
<RadioButton IsChecked="{Binding UseSpeedRatio}"
HorizontalContentAlignment="Stretch">
<DockPanel>
<TextBlock DockPanel.Dock="Left" Text="Speed ratio" />
<TextBlock DockPanel.Dock="Right"
Width="30"
Text="{Binding SpeedRatio}" />
<Slider Margin="5,0"
Minimum="0.1" Maximum="10"
TickPlacement="BottomRight"
Ticks="1,2,3,4,5,6,7,8,9,10"
IsSnapToTickEnabled="False"
Value="{Binding SpeedRatio, Mode=TwoWay}"/>
</DockPanel>
</RadioButton>
<RadioButton IsChecked="{Binding UseDuration}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Duration" />
<TextBox Text="{Binding Duration}" Width="75" Margin="5,0" />
</StackPanel>
</RadioButton>
</StackPanel>

</Grid>
</GroupBox>
<GroupBox Header="Control" DockPanel.Dock="Bottom">
Expand All @@ -94,6 +126,8 @@
Stretch="None"
gif:ImageBehavior.AnimatedSource="{Binding SelectedImage}"
gif:ImageBehavior.RepeatBehavior="{Binding RepeatBehavior}"
gif:ImageBehavior.AnimationSpeedRatio="{Binding ActualSpeedRatio}"
gif:ImageBehavior.AnimationDuration="{Binding ActualDuration}"
gif:ImageBehavior.AutoStart="{Binding AutoStart}"
gif:ImageBehavior.AnimationCompleted="AnimationCompleted" />

Expand Down
74 changes: 74 additions & 0 deletions WpfAnimatedGif.Demo/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,80 @@ public RepeatBehavior RepeatBehavior
}
}

private double _speedRatio = 1.0;
public double SpeedRatio
{
get => _speedRatio;
set
{
_speedRatio = value;
OnPropertyChanged(nameof(SpeedRatio));
OnPropertyChanged(nameof(ActualSpeedRatio));
}
}

private Duration _duration = TimeSpan.FromSeconds(3);
public Duration Duration
{
get => _duration;
set
{
_duration = value;
OnPropertyChanged(nameof(Duration));
OnPropertyChanged(nameof(ActualDuration));
}
}

private bool _useDefaultDuration = true;
public bool UseDefaultDuration
{
get => _useDefaultDuration;
set
{
_useDefaultDuration = value;
OnPropertyChanged(nameof(UseDefaultDuration));
OnPropertyChanged(nameof(ActualDuration));
OnPropertyChanged(nameof(ActualSpeedRatio));
}
}

private bool _useSpeedRatio = false;
public bool UseSpeedRatio
{
get => _useSpeedRatio;
set
{
_useSpeedRatio = value;
if (value)
{
UseDuration = false;
}
OnPropertyChanged(nameof(UseSpeedRatio));
OnPropertyChanged(nameof(ActualSpeedRatio));
OnPropertyChanged(nameof(ActualDuration));
}
}

private bool _useDuration = false;
public bool UseDuration
{
get => _useDuration;
set
{
_useDuration = value;
if (value)
{
UseSpeedRatio = false;
}
OnPropertyChanged(nameof(UseDuration));
OnPropertyChanged(nameof(ActualDuration));
OnPropertyChanged(nameof(ActualSpeedRatio));
}
}

public Duration? ActualDuration => UseDuration ? Duration : default(Duration?);
public double? ActualSpeedRatio => UseSpeedRatio ? SpeedRatio : default(double?);

private bool _autoStart = true;
public bool AutoStart
{
Expand Down
90 changes: 50 additions & 40 deletions WpfAnimatedGif/AnimationCache.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
Expand All @@ -8,21 +10,18 @@ namespace WpfAnimatedGif
{
static class AnimationCache
{
private class CacheKey
private struct CacheKey
{
private readonly ImageSource _source;
private readonly RepeatBehavior _repeatBehavior;

public CacheKey(ImageSource source, RepeatBehavior repeatBehavior)
public CacheKey(ImageSource source)
{
_source = source;
_repeatBehavior = repeatBehavior;
}

private bool Equals(CacheKey other)
{
return ImageEquals(_source, other._source)
&& Equals(_repeatBehavior, other._repeatBehavior);
return ImageEquals(_source, other._source);
}

public override bool Equals(object obj)
Expand All @@ -35,10 +34,7 @@ public override bool Equals(object obj)

public override int GetHashCode()
{
unchecked
{
return (ImageGetHashCode(_source) * 397) ^ _repeatBehavior.GetHashCode();
}
return ImageGetHashCode(_source);
}

private static int ImageGetHashCode(ImageSource image)
Expand Down Expand Up @@ -99,53 +95,67 @@ private static Uri GetUri(ImageSource image)
}
}

private static readonly Dictionary<CacheKey, ObjectAnimationUsingKeyFrames> _animationCache = new Dictionary<CacheKey, ObjectAnimationUsingKeyFrames>();
private static readonly Dictionary<CacheKey, int> _referenceCount = new Dictionary<CacheKey, int>();
private static readonly Dictionary<CacheKey, AnimationCacheEntry> _animationCache = new Dictionary<CacheKey, AnimationCacheEntry>();
private static readonly Dictionary<CacheKey, HashSet<Image>> _imageControls = new Dictionary<CacheKey, HashSet<Image>>();

public static void IncrementReferenceCount(ImageSource source, RepeatBehavior repeatBehavior)
public static void AddControlForSource(ImageSource source, Image imageControl)
{
var cacheKey = new CacheKey(source, repeatBehavior);
int count;
_referenceCount.TryGetValue(cacheKey, out count);
count++;
_referenceCount[cacheKey] = count;
var cacheKey = new CacheKey(source);
if (!_imageControls.TryGetValue(cacheKey, out var controls))
{
_imageControls[cacheKey] = controls = new HashSet<Image>();
}

controls.Add(imageControl);
}

public static void DecrementReferenceCount(ImageSource source, RepeatBehavior repeatBehavior)
public static void RemoveControlForSource(ImageSource source, Image imageControl)
{
var cacheKey = new CacheKey(source, repeatBehavior);
int count;
_referenceCount.TryGetValue(cacheKey, out count);
if (count > 0)
var cacheKey = new CacheKey(source);
if (_imageControls.TryGetValue(cacheKey, out var controls))
{
count--;
_referenceCount[cacheKey] = count;
}
if (count == 0)
{
_animationCache.Remove(cacheKey);
_referenceCount.Remove(cacheKey);
if (controls.Remove(imageControl))
{
if (controls.Count == 0)
{
_animationCache.Remove(cacheKey);
_imageControls.Remove(cacheKey);
}
}
}
}

public static void AddAnimation(ImageSource source, RepeatBehavior repeatBehavior, ObjectAnimationUsingKeyFrames animation)
public static void Add(ImageSource source, AnimationCacheEntry entry)
{
var key = new CacheKey(source, repeatBehavior);
_animationCache[key] = animation;
var key = new CacheKey(source);
_animationCache[key] = entry;
}

public static void RemoveAnimation(ImageSource source, RepeatBehavior repeatBehavior, ObjectAnimationUsingKeyFrames animation)
public static void Remove(ImageSource source)
{
var key = new CacheKey(source, repeatBehavior);
var key = new CacheKey(source);
_animationCache.Remove(key);
}

public static ObjectAnimationUsingKeyFrames GetAnimation(ImageSource source, RepeatBehavior repeatBehavior)
public static AnimationCacheEntry Get(ImageSource source)
{
var key = new CacheKey(source);
_animationCache.TryGetValue(key, out var entry);
return entry;
}
}

internal class AnimationCacheEntry
{
public AnimationCacheEntry(ObjectKeyFrameCollection keyFrames, Duration duration, int repeatCountFromMetadata)
{
var key = new CacheKey(source, repeatBehavior);
ObjectAnimationUsingKeyFrames animation;
_animationCache.TryGetValue(key, out animation);
return animation;
KeyFrames = keyFrames;
Duration = duration;
RepeatCountFromMetadata = repeatCountFromMetadata;
}

public ObjectKeyFrameCollection KeyFrames { get; }
public Duration Duration { get; }
public int RepeatCountFromMetadata { get; }
}
}
Loading