diff --git a/binding/SkiaSharp.Skottie/Animation.cs b/binding/SkiaSharp.Skottie/Animation.cs index e831627e28a..813eeda6270 100644 --- a/binding/SkiaSharp.Skottie/Animation.cs +++ b/binding/SkiaSharp.Skottie/Animation.cs @@ -1,7 +1,6 @@ -using System; -using System.ComponentModel; +#nullable enable +using System; using System.IO; -using System.Runtime.InteropServices; using SkiaSharp.SceneGraph; namespace SkiaSharp.Skottie @@ -22,27 +21,54 @@ void ISKNonVirtualReferenceCounted.UnreferenceNative () protected override void DisposeNative () => SkottieApi.skottie_animation_delete (Handle); - public static bool TryParse (string data, out Animation animation) + public static Animation? Parse (string json) => + TryParse (json, out var animation) + ? animation + : null; + + public static bool TryParse (string json, [System.Diagnostics.CodeAnalysis.NotNullWhen (true)] out Animation? animation) { - animation = GetObject (SkottieApi.skottie_animation_make_from_string (data, data.Length)); + _ = json ?? throw new ArgumentNullException (nameof (json)); + + animation = GetObject (SkottieApi.skottie_animation_make_from_string (json, json.Length)); return animation != null; } - public static bool TryCreate (Stream stream, out Animation animation) + public static Animation? Create (Stream stream) => + TryCreate (stream, out var animation) + ? animation + : null; + + public static bool TryCreate (Stream stream, [System.Diagnostics.CodeAnalysis.NotNullWhen (true)] out Animation? animation) { - using (var managed = new SKManagedStream (stream)) { - return TryCreate (managed, out animation); - } + _ = stream ?? throw new ArgumentNullException (nameof (stream)); + + using var managed = new SKManagedStream (stream); + return TryCreate (managed, out animation); } - public static bool TryCreate (SKStream stream, out Animation animation) + public static Animation? Create (SKStream stream) => + TryCreate (stream, out var animation) + ? animation + : null; + + public static bool TryCreate (SKStream stream, [System.Diagnostics.CodeAnalysis.NotNullWhen (true)] out Animation? animation) { + _ = stream ?? throw new ArgumentNullException (nameof (stream)); + animation = GetObject (SkottieApi.skottie_animation_make_from_stream (stream.Handle)); return animation != null; } - public static bool TryCreate (string path, out Animation animation) + public static Animation? Create (string path) => + TryCreate (path, out var animation) + ? animation + : null; + + public static bool TryCreate (string path, [System.Diagnostics.CodeAnalysis.NotNullWhen (true)] out Animation? animation) { + _ = path ?? throw new ArgumentNullException (nameof (path)); + animation = GetObject (SkottieApi.skottie_animation_make_from_file (path)); return animation != null; } @@ -53,17 +79,20 @@ public unsafe void Render (SKCanvas canvas, SKRect dst) public void Render (SKCanvas canvas, SKRect dst, AnimationRenderFlags flags) => SkottieApi.skottie_animation_render_with_flags (Handle, canvas.Handle, &dst, flags); - public void Seek (double t, InvalidationController ic = null) - => SkottieApi.skottie_animation_seek (Handle, (float)t, ic?.Handle ?? IntPtr.Zero); + public void Seek (double percent, InvalidationController? ic = null) + => SkottieApi.skottie_animation_seek (Handle, (float)percent, ic?.Handle ?? IntPtr.Zero); - public void SeekFrame (double t, InvalidationController ic = null) - => SkottieApi.skottie_animation_seek_frame (Handle, (float)t, ic?.Handle ?? IntPtr.Zero); + public void SeekFrame (double frame, InvalidationController? ic = null) + => SkottieApi.skottie_animation_seek_frame (Handle, (float)frame, ic?.Handle ?? IntPtr.Zero); - public void SeekFrameTime (double t, InvalidationController ic = null) - => SkottieApi.skottie_animation_seek_frame_time (Handle, (float)t, ic?.Handle ?? IntPtr.Zero); + public void SeekFrameTime (double seconds, InvalidationController? ic = null) + => SkottieApi.skottie_animation_seek_frame_time (Handle, (float)seconds, ic?.Handle ?? IntPtr.Zero); - public double Duration - => SkottieApi.skottie_animation_get_duration (Handle); + public void SeekFrameTime (TimeSpan time, InvalidationController? ic = null) + => SeekFrameTime (time.TotalSeconds, ic); + + public TimeSpan Duration + => TimeSpan.FromSeconds(SkottieApi.skottie_animation_get_duration (Handle)); public double Fps => SkottieApi.skottie_animation_get_fps (Handle); @@ -77,9 +106,7 @@ public double OutPoint public string Version { get { using var str = new SKString (); - SkottieApi.skottie_animation_get_version (Handle, str.Handle); - return str.ToString (); } } @@ -92,7 +119,7 @@ public unsafe SKSize Size { } } - internal static Animation GetObject (IntPtr handle) => + internal static Animation? GetObject (IntPtr handle) => handle == IntPtr.Zero ? null : new Animation (handle, true); } } diff --git a/samples/Gallery/Shared/Samples/SkottieSample.cs b/samples/Gallery/Shared/Samples/SkottieSample.cs index 6085f066285..3ee6e05c90f 100644 --- a/samples/Gallery/Shared/Samples/SkottieSample.cs +++ b/samples/Gallery/Shared/Samples/SkottieSample.cs @@ -1,7 +1,4 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.Linq; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using SkiaSharp; @@ -12,24 +9,12 @@ namespace SkiaSharpSample.Samples [Preserve(AllMembers = true)] public class SkottieSample : AnimatedSampleBase { - private readonly Animation _animation; + private Animation _animation; private Stopwatch _watch = new Stopwatch(); [Preserve] public SkottieSample() { - using var fileStream = new SKManagedStream(SampleMedia.Images.LottieLogo); - - if (SkiaSharp.Skottie.Animation.TryCreate(fileStream, out _animation)) - { - _animation.Seek(0, null); - - Console.WriteLine($"SkottieSample(): Version:{_animation.Version} Duration:{_animation.Duration} Fps:{_animation.Fps} InPoint:{_animation.InPoint} OutPoint:{_animation.OutPoint}"); - } - else - { - Console.WriteLine($"SkottieSample(): failed to load animation"); - } } public override string Title => "Skottie"; @@ -38,47 +23,35 @@ public SkottieSample() protected override async Task OnInit() { - try - { - await base.OnInit(); - - _watch.Start(); - } - catch (Exception e) - { - Console.WriteLine(e); - } + _animation = Animation.Create(SampleMedia.Images.LottieLogo); + _animation.Seek(0, null); + + _watch.Start(); + + await base.OnInit(); } protected override async Task OnUpdate(CancellationToken token) { - try - { - await Task.Delay(25, token); + if (_animation == null) + return; - _animation.SeekFrameTime((float)_watch.Elapsed.TotalSeconds, null); + await Task.Delay(25, token); - if(_watch.Elapsed.TotalSeconds > _animation.Duration) - { - _watch.Restart(); - } - } - catch(Exception e) - { - Console.WriteLine(e); - } + _animation.SeekFrameTime(_watch.Elapsed); + + if (_watch.Elapsed > _animation.Duration) + _watch.Restart(); } protected override void OnDrawSample(SKCanvas canvas, int width, int height) { - try - { - _animation.Render(canvas, new SKRect(0, 0, width, height)); - } - catch (Exception e) - { - Console.WriteLine(e); - } + if (_animation == null) + return; + + canvas.Clear(SKColors.White); + + _animation.Render(canvas, new SKRect(0, 0, width, height)); } } } diff --git a/tests/Tests/Skottie/AnimationTest.cs b/tests/Tests/Skottie/AnimationTest.cs index 7478c51de56..136bfa6f07c 100644 --- a/tests/Tests/Skottie/AnimationTest.cs +++ b/tests/Tests/Skottie/AnimationTest.cs @@ -1,93 +1,130 @@ using System; -using System.Collections.Generic; -using Xunit; using System.IO; using SkiaSharp.SceneGraph; using SkiaSharp.Skottie; +using Xunit; namespace SkiaSharp.Tests { public class AnimationTest : SKTest { - [Trait(CategoryKey, ApiCategory)] [SkippableFact] - public void When_Default_Make() + public void When_Default_TryParse() { var path = Path.Combine(PathToImages, "LottieLogo1.json"); + var result = Animation.TryParse(File.ReadAllText(path), out var animation); - var result = SkiaSharp.Skottie.Animation.TryParse(File.ReadAllText(path), out var animation); Assert.True(result); - Assert.NotEqual(IntPtr.Zero, animation?.Handle); + Assert.NotNull(animation); + Assert.NotEqual(IntPtr.Zero, animation.Handle); } - [Trait(CategoryKey, ApiCategory)] [SkippableFact] - public void When_Default_Make_From_SKStream() + public void When_Default_Parse() { var path = Path.Combine(PathToImages, "LottieLogo1.json"); + var animation = Animation.Parse(File.ReadAllText(path)); + + Assert.NotNull(animation); + Assert.NotEqual(IntPtr.Zero, animation.Handle); + } + [SkippableFact] + public void When_Default_TryCreate_From_SKStream() + { + var path = Path.Combine(PathToImages, "LottieLogo1.json"); using var fileStream = File.OpenRead(path); using var managedStream = new SKManagedStream(fileStream); - var result = SkiaSharp.Skottie.Animation.TryCreate(managedStream, out var animation); + var result = Animation.TryCreate(managedStream, out var animation); + Assert.True(result); - Assert.NotEqual(IntPtr.Zero, animation?.Handle); + Assert.NotNull(animation); + Assert.NotEqual(IntPtr.Zero, animation.Handle); } - [Trait(CategoryKey, ApiCategory)] [SkippableFact] - public void When_Default_Make_From_Stream() + public void When_Default_Create_From_SKStream() { var path = Path.Combine(PathToImages, "LottieLogo1.json"); + using var fileStream = File.OpenRead(path); + using var managedStream = new SKManagedStream(fileStream); + var animation = Animation.Create(managedStream); + Assert.NotNull(animation); + Assert.NotEqual(IntPtr.Zero, animation.Handle); + } + + [SkippableFact] + public void When_Default_TryCreate_From_Stream() + { + var path = Path.Combine(PathToImages, "LottieLogo1.json"); using var fileStream = File.OpenRead(path); - var result = SkiaSharp.Skottie.Animation.TryCreate(fileStream, out var animation); + var result = Animation.TryCreate(fileStream, out var animation); + Assert.True(result); Assert.NotEqual(IntPtr.Zero, animation?.Handle); } - [Trait(CategoryKey, ApiCategory)] [SkippableFact] - public void When_Seek_Without_Controller() + public void When_Default_Create_From_Stream() { var path = Path.Combine(PathToImages, "LottieLogo1.json"); - using var fileStream = File.OpenRead(path); - var result = SkiaSharp.Skottie.Animation.TryCreate(fileStream, out var animation); - Assert.True(result); - Assert.NotEqual(IntPtr.Zero, animation?.Handle); + var animation = Animation.Create(fileStream); - animation.Seek(.1); + Assert.NotNull(animation); + Assert.NotEqual(IntPtr.Zero, animation.Handle); } - [Trait(CategoryKey, ApiCategory)] [SkippableFact] - public void When_Seek_With_Controller() + public void When_Default_TryCreate_From_File() { var path = Path.Combine(PathToImages, "LottieLogo1.json"); + var result = Animation.TryCreate(path, out var animation); - using var fileStream = File.OpenRead(path); - var result = SkiaSharp.Skottie.Animation.TryCreate(fileStream, out var animation); Assert.True(result); Assert.NotEqual(IntPtr.Zero, animation?.Handle); + } - var controller = new InvalidationController(); + [SkippableFact] + public void When_Default_Create_From_File() + { + var path = Path.Combine(PathToImages, "LottieLogo1.json"); + var animation = Animation.Create(path); - animation.Seek(.1, controller); + Assert.NotNull(animation); + Assert.NotEqual(IntPtr.Zero, animation.Handle); } private Animation BuildDefaultAnimation() { var path = Path.Combine(PathToImages, "LottieLogo1.json"); + var result = Animation.TryCreate(path, out var animation); - using var fileStream = File.OpenRead(path); - var result = SkiaSharp.Skottie.Animation.TryCreate(fileStream, out var animation); Assert.True(result); Assert.NotEqual(IntPtr.Zero, animation?.Handle); return animation; } - [Trait(CategoryKey, ApiCategory)] + [SkippableFact] + public void When_Seek_Without_Controller() + { + var animation = BuildDefaultAnimation(); + + animation.Seek(.1); + } + + [SkippableFact] + public void When_Seek_With_Controller() + { + var animation = BuildDefaultAnimation(); + + var controller = new InvalidationController(); + + animation.Seek(.1, controller); + } + [SkippableFact] public void When_SeekFrame_Without_Controller() { @@ -96,7 +133,6 @@ public void When_SeekFrame_Without_Controller() animation.SeekFrame(.1); } - [Trait(CategoryKey, ApiCategory)] [SkippableFact] public void When_SeekFrame_With_Controller() { @@ -107,7 +143,6 @@ public void When_SeekFrame_With_Controller() animation.SeekFrame(.1, controller); } - [Trait(CategoryKey, ApiCategory)] [SkippableFact] public void When_SeekFrameTime_Without_Controller() { @@ -116,7 +151,6 @@ public void When_SeekFrameTime_Without_Controller() animation.SeekFrameTime(.1); } - [Trait(CategoryKey, ApiCategory)] [SkippableFact] public void When_SeekFrameTime_With_Controller() { @@ -127,58 +161,70 @@ public void When_SeekFrameTime_With_Controller() animation.SeekFrameTime(.1, controller); } - [Trait(CategoryKey, ApiCategory)] + [SkippableFact] + public void When_SeekFrameTimeSpan_Without_Controller() + { + var animation = BuildDefaultAnimation(); + + animation.SeekFrameTime(TimeSpan.FromSeconds(.1)); + } + + [SkippableFact] + public void When_SeekFrameTimeSpan_With_Controller() + { + var animation = BuildDefaultAnimation(); + + var controller = new InvalidationController(); + + animation.SeekFrameTime(TimeSpan.FromSeconds(.1), controller); + } + [SkippableFact] public void When_Duration() { var animation = BuildDefaultAnimation(); - Assert.True(animation.Duration > 0); + Assert.Equal(TimeSpan.FromSeconds(5.9666666), animation.Duration); } - [Trait(CategoryKey, ApiCategory)] [SkippableFact] public void When_Fps() { var animation = BuildDefaultAnimation(); - Assert.True(animation.Fps == 30); + Assert.Equal(30, animation.Fps); } - [Trait(CategoryKey, ApiCategory)] [SkippableFact] public void When_InPoint() { var animation = BuildDefaultAnimation(); - Assert.True(animation.InPoint == 0); + Assert.Equal(0, animation.InPoint); } - [Trait(CategoryKey, ApiCategory)] [SkippableFact] public void When_OutPoint() { var animation = BuildDefaultAnimation(); - Assert.True(animation.OutPoint > 0); + Assert.Equal(179, animation.OutPoint); } - [Trait(CategoryKey, ApiCategory)] [SkippableFact] public void When_Version() { var animation = BuildDefaultAnimation(); - Assert.True(animation.Version.Length > 0); + Assert.Equal("4.4.26", animation.Version); } - [Trait(CategoryKey, ApiCategory)] [SkippableFact] public void When_Size() { var animation = BuildDefaultAnimation(); - Assert.True(animation.Size.Height > 0); + Assert.Equal(new SKSize(375, 667), animation.Size); } } }