88
99namespace Microsoft . UI . Composition ;
1010
11- internal sealed class GifFrameProvider : IFrameProvider
11+ internal sealed class AnimatedImageFrameProvider : IFrameProvider
1212{
1313 private readonly SKImage [ ] _images ;
14- private readonly SKCodecFrameInfo [ ] ? _frameInfos ;
14+ private readonly int [ ] _durations ;
1515 private readonly Timer ? _timer ;
1616 private readonly Stopwatch ? _stopwatch ;
1717 private readonly long _totalDuration ;
18+ private readonly long _memoryPressure ;
1819 private readonly WeakReference < Action > _onFrameChanged ;
1920
2021 private int _currentFrame ;
2122 private bool _disposed ;
2223
23- // Note: The Timer will keep holding onto the ImageFrameProvider until stopped (it's a static root).
24- // But we only stop the timer when we dispose ImageFrameProvider from SkiaCompositionSurface finalizer.
24+ // Note: The Timer will keep holding onto the AnimatedImageFrameProvider until stopped (it's a static root).
25+ // But we only stop the timer when we dispose AnimatedImageFrameProvider from SkiaCompositionSurface finalizer.
2526 // The onFrameChanged Action is also holding onto SkiaCompositionSurface.
26- // So, if ImageFrameProvider holds onto onFrameChanged, the SkiaCompositionSurface is never GC'ed.
27+ // So, if AnimatedImageFrameProvider holds onto onFrameChanged, the SkiaCompositionSurface is never GC'ed.
2728 // That's why we make it a WeakReference.
2829 // Note that SkiaCompositionSurface keeps an unused private field storing onFrameChanged so that it's not GC'ed early.
29- internal GifFrameProvider ( SKImage [ ] images , SKCodecFrameInfo [ ] frameInfos , long totalDuration , Action onFrameChanged )
30+ internal AnimatedImageFrameProvider ( SKImage [ ] images , int [ ] durations , long totalDuration , Action onFrameChanged )
3031 {
3132 _images = images ;
32- _frameInfos = frameInfos ;
33+ _durations = durations ;
3334 _totalDuration = totalDuration ;
3435 _onFrameChanged = new WeakReference < Action > ( onFrameChanged ) ;
3536 Debug . Assert ( images . Length > 1 ) ;
36- Debug . Assert ( frameInfos is not null ) ;
37+ Debug . Assert ( durations is not null ) ;
38+ Debug . Assert ( durations . Length == images . Length ) ;
3739 Debug . Assert ( totalDuration != 0 ) ;
3840 Debug . Assert ( onFrameChanged is not null ) ;
3941
4042 if ( _images . Length < 2 )
4143 {
42- throw new ArgumentException ( "GifFrameProvider should only be used when there is at least two frames" ) ;
44+ throw new ArgumentException ( "AnimatedImageFrameProvider should only be used when there is at least two frames" ) ;
4345 }
4446
47+ long pressure = 0 ;
48+ for ( int i = 0 ; i < _images . Length ; i ++ )
49+ {
50+ pressure += _images [ i ] . Info . BytesSize ;
51+ }
52+
53+ _memoryPressure = pressure ;
54+ GC . AddMemoryPressure ( _memoryPressure ) ;
55+
4556 _stopwatch = Stopwatch . StartNew ( ) ;
46- _timer = new Timer ( OnTimerCallback , null , dueTime : _frameInfos ! [ 0 ] . Duration , period : Timeout . Infinite ) ;
57+ _timer = new Timer ( OnTimerCallback , null , dueTime : _durations [ 0 ] , period : Timeout . Infinite ) ;
4758 }
4859
4960 public SKImage ? CurrentImage => _images [ _currentFrame ] ;
5061
5162 private int GetCurrentFrameIndex ( )
5263 {
5364 var currentTimestampInMilliseconds = _stopwatch ! . ElapsedMilliseconds % _totalDuration ;
54- for ( int i = 0 ; i < _frameInfos ! . Length ; i ++ )
65+ for ( int i = 0 ; i < _durations . Length ; i ++ )
5566 {
56- if ( currentTimestampInMilliseconds < _frameInfos [ i ] . Duration )
67+ if ( currentTimestampInMilliseconds < _durations [ i ] )
5768 {
5869 return i ;
5970 }
6071
61- currentTimestampInMilliseconds -= _frameInfos [ i ] . Duration ;
72+ currentTimestampInMilliseconds -= _durations [ i ] ;
6273 }
6374
6475 throw new InvalidOperationException ( "This shouldn't be reachable. A timestamp in total duration range should map to a frame" ) ;
@@ -86,7 +97,7 @@ private void OnTimerCallback(object? state)
8697 var nextFrameTimeStamp = 0 ;
8798 for ( int i = 0 ; i <= _currentFrame ; i ++ )
8899 {
89- nextFrameTimeStamp += _frameInfos ! [ i ] . Duration ;
100+ nextFrameTimeStamp += _durations [ i ] ;
90101 }
91102
92103 var dueTime = nextFrameTimeStamp - timestamp ;
@@ -109,8 +120,15 @@ public void Dispose()
109120 {
110121 if ( ! _disposed )
111122 {
112- _timer ? . Dispose ( ) ;
113123 _disposed = true ;
124+ _timer ? . Dispose ( ) ;
125+
126+ for ( int i = 0 ; i < _images . Length ; i ++ )
127+ {
128+ _images [ i ] . Dispose ( ) ;
129+ }
130+
131+ GC . RemoveMemoryPressure ( _memoryPressure ) ;
114132 }
115133 }
116134}
0 commit comments