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

Support loading animations with BOM #2167

Merged
merged 2 commits into from
Jul 14, 2022
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
2 changes: 1 addition & 1 deletion binding/Binding/SKData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public static SKData Create (Stream stream)
if (stream == null)
throw new ArgumentNullException (nameof (stream));
if (stream.CanSeek) {
return Create (stream, stream.Length);
return Create (stream, stream.Length - stream.Position);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a bug I found when you pass a stream that was not at 0. This can be used to create a data object from a section of a stream.

} else {
using var memory = new SKDynamicMemoryWStream ();
using (var managed = new SKManagedStream (stream)) {
Expand Down
34 changes: 24 additions & 10 deletions binding/Binding/Util.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,25 @@ internal unsafe static class Utils
{
internal const float NearlyZero = 1.0f / (1 << 12);

internal static int GetPreambleSize (SKData data)
{
_ = data ?? throw new ArgumentNullException (nameof (data));

var buffer = data.AsSpan ();
var len = buffer.Length;

if (len >= 2 && buffer[0] == 0xfe && buffer[1] == 0xff)
return 2;
else if (len >= 3 && buffer[0] == 0xef && buffer[1] == 0xbb && buffer[2] == 0xbf)
return 3;
else if (len >= 3 && buffer[0] == 0x2b && buffer[1] == 0x2f && buffer[2] == 0x76)
return 3;
else if (len >= 4 && buffer[0] == 0 && buffer[1] == 0 && buffer[2] == 0xfe && buffer[3] == 0xff)
return 4;
Comment on lines +22 to +29
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BOM detection. Right now just the basic UTF-7/8/16/32

else
return 0;
}

internal static Span<byte> AsSpan (this IntPtr ptr, int size) =>
new Span<byte> ((void*)ptr, size);

Expand Down Expand Up @@ -105,8 +124,7 @@ public unsafe static class StringUtilities
// GetUnicodeStringLength

private static int GetUnicodeStringLength (SKTextEncoding encoding) =>
encoding switch
{
encoding switch {
SKTextEncoding.Utf8 => 1,
SKTextEncoding.Utf16 => 1,
SKTextEncoding.Utf32 => 2,
Expand All @@ -116,8 +134,7 @@ private static int GetUnicodeStringLength (SKTextEncoding encoding) =>
// GetCharacterByteSize

internal static int GetCharacterByteSize (this SKTextEncoding encoding) =>
encoding switch
{
encoding switch {
SKTextEncoding.Utf8 => 1,
SKTextEncoding.Utf16 => 2,
SKTextEncoding.Utf32 => 4,
Expand Down Expand Up @@ -156,8 +173,7 @@ internal static byte[] GetEncodedText (string text, SKTextEncoding encoding, boo
}

public static byte[] GetEncodedText (ReadOnlySpan<char> text, SKTextEncoding encoding) =>
encoding switch
{
encoding switch {
SKTextEncoding.Utf8 => Encoding.UTF8.GetBytes (text),
SKTextEncoding.Utf16 => Encoding.Unicode.GetBytes (text),
SKTextEncoding.Utf32 => Encoding.UTF32.GetBytes (text),
Expand All @@ -177,8 +193,7 @@ public static string GetString (byte[] data, int index, int count, SKTextEncodin
if (data == null)
throw new ArgumentNullException (nameof (data));

return encoding switch
{
return encoding switch {
SKTextEncoding.Utf8 => Encoding.UTF8.GetString (data, index, count),
SKTextEncoding.Utf16 => Encoding.Unicode.GetString (data, index, count),
SKTextEncoding.Utf32 => Encoding.UTF32.GetString (data, index, count),
Expand All @@ -197,8 +212,7 @@ public static string GetString (ReadOnlySpan<byte> data, int index, int count, S
return string.Empty;

fixed (byte* bp = data) {
return encoding switch
{
return encoding switch {
SKTextEncoding.Utf8 => Encoding.UTF8.GetString (bp, data.Length),
SKTextEncoding.Utf16 => Encoding.Unicode.GetString (bp, data.Length),
SKTextEncoding.Utf32 => Encoding.UTF32.GetString (bp, data.Length),
Expand Down
27 changes: 21 additions & 6 deletions binding/SkiaSharp.Skottie/Animation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ void ISKNonVirtualReferenceCounted.UnreferenceNative ()
protected override void DisposeNative ()
=> SkottieApi.skottie_animation_delete (Handle);

// Parse

public static Animation? Parse (string json) =>
TryParse (json, out var animation)
? animation
Expand All @@ -34,6 +36,8 @@ public static bool TryParse (string json, [System.Diagnostics.CodeAnalysis.NotNu
return animation != null;
}

// Create

public static Animation? Create (Stream stream) =>
TryCreate (stream, out var animation)
? animation
Expand All @@ -56,8 +60,8 @@ public static bool TryCreate (SKStream stream, [System.Diagnostics.CodeAnalysis.
{
_ = stream ?? throw new ArgumentNullException (nameof (stream));

animation = GetObject (SkottieApi.skottie_animation_make_from_stream (stream.Handle));
return animation != null;
using var data = SKData.Create (stream);
return TryCreate (data, out animation);
Comment on lines -59 to +64
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All animations are now created through the SKData overload, which we then use to detect the BOM.

We could use the stream, but then we have to use buffering and all other manner of complex mechanisms. This way we load the full data, and then optionally skip 4 bytes before passing it to C++.

}

public static Animation? Create (SKData data) =>
Expand All @@ -69,8 +73,13 @@ public static bool TryCreate (SKData data, [System.Diagnostics.CodeAnalysis.NotN
{
_ = data ?? throw new ArgumentNullException (nameof (data));

animation = GetObject (SkottieApi.skottie_animation_make_from_data ((void*)data.Data, (IntPtr)data.Size));
return animation != null;
var preamble = Utils.GetPreambleSize (data);
var span = data.AsSpan ().Slice (preamble);

fixed (byte* ptr = span) {
animation = GetObject (SkottieApi.skottie_animation_make_from_data (ptr, (IntPtr)span.Length));
return animation != null;
}
}

public static Animation? Create (string path) =>
Expand All @@ -82,16 +91,20 @@ public static bool TryCreate (string path, [System.Diagnostics.CodeAnalysis.NotN
{
_ = path ?? throw new ArgumentNullException (nameof (path));

animation = GetObject (SkottieApi.skottie_animation_make_from_file (path));
return animation != null;
using var data = SKData.Create (path);
return TryCreate (data, out animation);
}

// Render

public unsafe void Render (SKCanvas canvas, SKRect dst)
=> SkottieApi.skottie_animation_render (Handle, canvas.Handle, &dst);

public void Render (SKCanvas canvas, SKRect dst, AnimationRenderFlags flags)
=> SkottieApi.skottie_animation_render_with_flags (Handle, canvas.Handle, &dst, flags);

// Seek*

public void Seek (double percent, InvalidationController? ic = null)
=> SkottieApi.skottie_animation_seek (Handle, (float)percent, ic?.Handle ?? IntPtr.Zero);

Expand All @@ -104,6 +117,8 @@ public void SeekFrameTime (double seconds, InvalidationController? ic = null)
public void SeekFrameTime (TimeSpan time, InvalidationController? ic = null)
=> SeekFrameTime (time.TotalSeconds, ic);

// Properties

public TimeSpan Duration
=> TimeSpan.FromSeconds (SkottieApi.skottie_animation_get_duration (Handle));

Expand Down
2 changes: 1 addition & 1 deletion scripts/azure-pipelines-variables.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ variables:
MONO_VERSION_LINUX: ''
XCODE_VERSION: 13.2.1
VISUAL_STUDIO_VERSION: '17/pre'
DOTNET_VERSION_PREVIEW: '6.0.301'
DOTNET_VERSION_PREVIEW: '6.0.302'
DOTNET_WORKLOAD_SOURCE: 'https://aka.ms/dotnet/maui/6.0.300.json'
CONFIGURATION: 'Release'
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
Expand Down
Loading