Support loading animations with BOM (#2167)

* Support loading animations with BOM

Fixes #2166

* New .NET version that just came out
This commit is contained in:
Matthew Leibowitz 2022-07-14 05:26:52 +02:00 коммит произвёл GitHub
Родитель 0f250519aa
Коммит 8f81be2691
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
7 изменённых файлов: 7529 добавлений и 54 удалений

Просмотреть файл

@ -103,7 +103,7 @@ namespace SkiaSharp
if (stream == null) if (stream == null)
throw new ArgumentNullException (nameof (stream)); throw new ArgumentNullException (nameof (stream));
if (stream.CanSeek) { if (stream.CanSeek) {
return Create (stream, stream.Length); return Create (stream, stream.Length - stream.Position);
} else { } else {
using var memory = new SKDynamicMemoryWStream (); using var memory = new SKDynamicMemoryWStream ();
using (var managed = new SKManagedStream (stream)) { using (var managed = new SKManagedStream (stream)) {

Просмотреть файл

@ -12,6 +12,25 @@ namespace SkiaSharp
{ {
internal const float NearlyZero = 1.0f / (1 << 12); 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;
else
return 0;
}
internal static Span<byte> AsSpan (this IntPtr ptr, int size) => internal static Span<byte> AsSpan (this IntPtr ptr, int size) =>
new Span<byte> ((void*)ptr, size); new Span<byte> ((void*)ptr, size);
@ -105,8 +124,7 @@ namespace SkiaSharp
// GetUnicodeStringLength // GetUnicodeStringLength
private static int GetUnicodeStringLength (SKTextEncoding encoding) => private static int GetUnicodeStringLength (SKTextEncoding encoding) =>
encoding switch encoding switch {
{
SKTextEncoding.Utf8 => 1, SKTextEncoding.Utf8 => 1,
SKTextEncoding.Utf16 => 1, SKTextEncoding.Utf16 => 1,
SKTextEncoding.Utf32 => 2, SKTextEncoding.Utf32 => 2,
@ -116,8 +134,7 @@ namespace SkiaSharp
// GetCharacterByteSize // GetCharacterByteSize
internal static int GetCharacterByteSize (this SKTextEncoding encoding) => internal static int GetCharacterByteSize (this SKTextEncoding encoding) =>
encoding switch encoding switch {
{
SKTextEncoding.Utf8 => 1, SKTextEncoding.Utf8 => 1,
SKTextEncoding.Utf16 => 2, SKTextEncoding.Utf16 => 2,
SKTextEncoding.Utf32 => 4, SKTextEncoding.Utf32 => 4,
@ -156,8 +173,7 @@ namespace SkiaSharp
} }
public static byte[] GetEncodedText (ReadOnlySpan<char> text, SKTextEncoding encoding) => public static byte[] GetEncodedText (ReadOnlySpan<char> text, SKTextEncoding encoding) =>
encoding switch encoding switch {
{
SKTextEncoding.Utf8 => Encoding.UTF8.GetBytes (text), SKTextEncoding.Utf8 => Encoding.UTF8.GetBytes (text),
SKTextEncoding.Utf16 => Encoding.Unicode.GetBytes (text), SKTextEncoding.Utf16 => Encoding.Unicode.GetBytes (text),
SKTextEncoding.Utf32 => Encoding.UTF32.GetBytes (text), SKTextEncoding.Utf32 => Encoding.UTF32.GetBytes (text),
@ -177,8 +193,7 @@ namespace SkiaSharp
if (data == null) if (data == null)
throw new ArgumentNullException (nameof (data)); throw new ArgumentNullException (nameof (data));
return encoding switch return encoding switch {
{
SKTextEncoding.Utf8 => Encoding.UTF8.GetString (data, index, count), SKTextEncoding.Utf8 => Encoding.UTF8.GetString (data, index, count),
SKTextEncoding.Utf16 => Encoding.Unicode.GetString (data, index, count), SKTextEncoding.Utf16 => Encoding.Unicode.GetString (data, index, count),
SKTextEncoding.Utf32 => Encoding.UTF32.GetString (data, index, count), SKTextEncoding.Utf32 => Encoding.UTF32.GetString (data, index, count),
@ -197,8 +212,7 @@ namespace SkiaSharp
return string.Empty; return string.Empty;
fixed (byte* bp = data) { fixed (byte* bp = data) {
return encoding switch return encoding switch {
{
SKTextEncoding.Utf8 => Encoding.UTF8.GetString (bp, data.Length), SKTextEncoding.Utf8 => Encoding.UTF8.GetString (bp, data.Length),
SKTextEncoding.Utf16 => Encoding.Unicode.GetString (bp, data.Length), SKTextEncoding.Utf16 => Encoding.Unicode.GetString (bp, data.Length),
SKTextEncoding.Utf32 => Encoding.UTF32.GetString (bp, data.Length), SKTextEncoding.Utf32 => Encoding.UTF32.GetString (bp, data.Length),

Просмотреть файл

@ -21,6 +21,8 @@ namespace SkiaSharp.Skottie
protected override void DisposeNative () protected override void DisposeNative ()
=> SkottieApi.skottie_animation_delete (Handle); => SkottieApi.skottie_animation_delete (Handle);
// Parse
public static Animation? Parse (string json) => public static Animation? Parse (string json) =>
TryParse (json, out var animation) TryParse (json, out var animation)
? animation ? animation
@ -34,6 +36,8 @@ namespace SkiaSharp.Skottie
return animation != null; return animation != null;
} }
// Create
public static Animation? Create (Stream stream) => public static Animation? Create (Stream stream) =>
TryCreate (stream, out var animation) TryCreate (stream, out var animation)
? animation ? animation
@ -56,8 +60,8 @@ namespace SkiaSharp.Skottie
{ {
_ = stream ?? throw new ArgumentNullException (nameof (stream)); _ = stream ?? throw new ArgumentNullException (nameof (stream));
animation = GetObject (SkottieApi.skottie_animation_make_from_stream (stream.Handle)); using var data = SKData.Create (stream);
return animation != null; return TryCreate (data, out animation);
} }
public static Animation? Create (SKData data) => public static Animation? Create (SKData data) =>
@ -69,8 +73,13 @@ namespace SkiaSharp.Skottie
{ {
_ = data ?? throw new ArgumentNullException (nameof (data)); _ = data ?? throw new ArgumentNullException (nameof (data));
animation = GetObject (SkottieApi.skottie_animation_make_from_data ((void*)data.Data, (IntPtr)data.Size)); var preamble = Utils.GetPreambleSize (data);
return animation != null; 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) => public static Animation? Create (string path) =>
@ -82,16 +91,20 @@ namespace SkiaSharp.Skottie
{ {
_ = path ?? throw new ArgumentNullException (nameof (path)); _ = path ?? throw new ArgumentNullException (nameof (path));
animation = GetObject (SkottieApi.skottie_animation_make_from_file (path)); using var data = SKData.Create (path);
return animation != null; return TryCreate (data, out animation);
} }
// Render
public unsafe void Render (SKCanvas canvas, SKRect dst) public unsafe void Render (SKCanvas canvas, SKRect dst)
=> SkottieApi.skottie_animation_render (Handle, canvas.Handle, &dst); => SkottieApi.skottie_animation_render (Handle, canvas.Handle, &dst);
public void Render (SKCanvas canvas, SKRect dst, AnimationRenderFlags flags) public void Render (SKCanvas canvas, SKRect dst, AnimationRenderFlags flags)
=> SkottieApi.skottie_animation_render_with_flags (Handle, canvas.Handle, &dst, flags); => SkottieApi.skottie_animation_render_with_flags (Handle, canvas.Handle, &dst, flags);
// Seek*
public void Seek (double percent, InvalidationController? ic = null) public void Seek (double percent, InvalidationController? ic = null)
=> SkottieApi.skottie_animation_seek (Handle, (float)percent, ic?.Handle ?? IntPtr.Zero); => SkottieApi.skottie_animation_seek (Handle, (float)percent, ic?.Handle ?? IntPtr.Zero);
@ -104,6 +117,8 @@ namespace SkiaSharp.Skottie
public void SeekFrameTime (TimeSpan time, InvalidationController? ic = null) public void SeekFrameTime (TimeSpan time, InvalidationController? ic = null)
=> SeekFrameTime (time.TotalSeconds, ic); => SeekFrameTime (time.TotalSeconds, ic);
// Properties
public TimeSpan Duration public TimeSpan Duration
=> TimeSpan.FromSeconds (SkottieApi.skottie_animation_get_duration (Handle)); => TimeSpan.FromSeconds (SkottieApi.skottie_animation_get_duration (Handle));

Просмотреть файл

@ -15,7 +15,7 @@ variables:
MONO_VERSION_LINUX: '' MONO_VERSION_LINUX: ''
XCODE_VERSION: 13.2.1 XCODE_VERSION: 13.2.1
VISUAL_STUDIO_VERSION: '17/pre' 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' DOTNET_WORKLOAD_SOURCE: 'https://aka.ms/dotnet/maui/6.0.300.json'
CONFIGURATION: 'Release' CONFIGURATION: 'Release'
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -179,6 +179,32 @@ namespace SkiaSharp.Tests
Assert.NotNull(data); Assert.NotNull(data);
} }
[SkippableFact]
public void CanCreateFromPartiallyReadStream()
{
using var stream = File.OpenRead(Path.Combine(PathToImages, "baboon.png"));
stream.Position = 10;
using var data = SKData.Create(stream);
Assert.NotNull(data);
Assert.Equal(stream.Length - 10, data.Size);
}
[SkippableFact]
public void CanCreateFromPartiallyReadNonSeekable()
{
using var stream = File.OpenRead(Path.Combine(PathToImages, "baboon.png"));
stream.Position = 10;
using var nonSeekable = new NonSeekableReadOnlyStream(stream);
using var data = SKData.Create(nonSeekable);
Assert.NotNull(data);
Assert.Equal(stream.Length - 10, data.Size);
}
[SkippableFact(Skip = "Doesn't work as it relies on memory being overwritten by an external process.")] [SkippableFact(Skip = "Doesn't work as it relies on memory being overwritten by an external process.")]
public void DataDisposedReturnsInvalidStream() public void DataDisposedReturnsInvalidStream()
{ {

Просмотреть файл

@ -8,10 +8,29 @@ namespace SkiaSharp.Tests
{ {
public class AnimationTest : SKTest public class AnimationTest : SKTest
{ {
[SkippableFact] public static TheoryData<string> DefaultLottieFiles =>
public void When_Default_TryParse() new TheoryData<string>
{
"LottieLogo1.json",
"LottieLogo1_bom.json",
};
[SkippableTheory]
[InlineData("LottieLogo1.json", 0)]
[InlineData("LottieLogo1_bom.json", 3)]
public void EnsureLottieHasCorrectPreamble(string filename, int preamble)
{ {
var path = Path.Combine(PathToImages, "LottieLogo1.json"); var path = Path.Combine(PathToImages, filename);
var data = SKData.Create(path);
Assert.Equal(preamble, Utils.GetPreambleSize(data));
}
[SkippableTheory]
[MemberData(nameof(DefaultLottieFiles))]
public void When_Default_TryParse(string filename)
{
var path = Path.Combine(PathToImages, filename);
var result = Animation.TryParse(File.ReadAllText(path), out var animation); var result = Animation.TryParse(File.ReadAllText(path), out var animation);
Assert.True(result); Assert.True(result);
@ -19,20 +38,22 @@ namespace SkiaSharp.Tests
Assert.NotEqual(IntPtr.Zero, animation.Handle); Assert.NotEqual(IntPtr.Zero, animation.Handle);
} }
[SkippableFact] [SkippableTheory]
public void When_Default_Parse() [MemberData(nameof(DefaultLottieFiles))]
public void When_Default_Parse(string filename)
{ {
var path = Path.Combine(PathToImages, "LottieLogo1.json"); var path = Path.Combine(PathToImages, filename);
var animation = Animation.Parse(File.ReadAllText(path)); var animation = Animation.Parse(File.ReadAllText(path));
Assert.NotNull(animation); Assert.NotNull(animation);
Assert.NotEqual(IntPtr.Zero, animation.Handle); Assert.NotEqual(IntPtr.Zero, animation.Handle);
} }
[SkippableFact] [SkippableTheory]
public void When_Default_TryCreate_From_SKData() [MemberData(nameof(DefaultLottieFiles))]
public void When_Default_TryCreate_From_SKData(string filename)
{ {
var path = Path.Combine(PathToImages, "LottieLogo1.json"); var path = Path.Combine(PathToImages, filename);
using var data = SKData.Create(path); using var data = SKData.Create(path);
var result = Animation.TryCreate(data, out var animation); var result = Animation.TryCreate(data, out var animation);
@ -41,10 +62,11 @@ namespace SkiaSharp.Tests
Assert.NotEqual(IntPtr.Zero, animation.Handle); Assert.NotEqual(IntPtr.Zero, animation.Handle);
} }
[SkippableFact] [SkippableTheory]
public void When_Default_Create_From_SKData() [MemberData(nameof(DefaultLottieFiles))]
public void When_Default_Create_From_SKData(string filename)
{ {
var path = Path.Combine(PathToImages, "LottieLogo1.json"); var path = Path.Combine(PathToImages, filename);
using var data = SKData.Create(path); using var data = SKData.Create(path);
var animation = Animation.Create(data); var animation = Animation.Create(data);
@ -52,10 +74,11 @@ namespace SkiaSharp.Tests
Assert.NotEqual(IntPtr.Zero, animation.Handle); Assert.NotEqual(IntPtr.Zero, animation.Handle);
} }
[SkippableFact] [SkippableTheory]
public void When_Default_TryCreate_From_SKStream() [MemberData(nameof(DefaultLottieFiles))]
public void When_Default_TryCreate_From_SKStream(string filename)
{ {
var path = Path.Combine(PathToImages, "LottieLogo1.json"); var path = Path.Combine(PathToImages, filename);
using var fileStream = File.OpenRead(path); using var fileStream = File.OpenRead(path);
using var managedStream = new SKManagedStream(fileStream); using var managedStream = new SKManagedStream(fileStream);
var result = Animation.TryCreate(managedStream, out var animation); var result = Animation.TryCreate(managedStream, out var animation);
@ -65,10 +88,11 @@ namespace SkiaSharp.Tests
Assert.NotEqual(IntPtr.Zero, animation.Handle); Assert.NotEqual(IntPtr.Zero, animation.Handle);
} }
[SkippableFact] [SkippableTheory]
public void When_Default_Create_From_SKStream() [MemberData(nameof(DefaultLottieFiles))]
public void When_Default_Create_From_SKStream(string filename)
{ {
var path = Path.Combine(PathToImages, "LottieLogo1.json"); var path = Path.Combine(PathToImages, filename);
using var fileStream = File.OpenRead(path); using var fileStream = File.OpenRead(path);
using var managedStream = new SKManagedStream(fileStream); using var managedStream = new SKManagedStream(fileStream);
var animation = Animation.Create(managedStream); var animation = Animation.Create(managedStream);
@ -77,10 +101,11 @@ namespace SkiaSharp.Tests
Assert.NotEqual(IntPtr.Zero, animation.Handle); Assert.NotEqual(IntPtr.Zero, animation.Handle);
} }
[SkippableFact] [SkippableTheory]
public void When_Default_TryCreate_From_Stream() [MemberData(nameof(DefaultLottieFiles))]
public void When_Default_TryCreate_From_Stream(string filename)
{ {
var path = Path.Combine(PathToImages, "LottieLogo1.json"); var path = Path.Combine(PathToImages, filename);
using var fileStream = File.OpenRead(path); using var fileStream = File.OpenRead(path);
var result = Animation.TryCreate(fileStream, out var animation); var result = Animation.TryCreate(fileStream, out var animation);
@ -88,10 +113,11 @@ namespace SkiaSharp.Tests
Assert.NotEqual(IntPtr.Zero, animation?.Handle); Assert.NotEqual(IntPtr.Zero, animation?.Handle);
} }
[SkippableFact] [SkippableTheory]
public void When_Default_Create_From_Stream() [MemberData(nameof(DefaultLottieFiles))]
public void When_Default_Create_From_Stream(string filename)
{ {
var path = Path.Combine(PathToImages, "LottieLogo1.json"); var path = Path.Combine(PathToImages, filename);
using var fileStream = File.OpenRead(path); using var fileStream = File.OpenRead(path);
var animation = Animation.Create(fileStream); var animation = Animation.Create(fileStream);
@ -99,10 +125,11 @@ namespace SkiaSharp.Tests
Assert.NotEqual(IntPtr.Zero, animation.Handle); Assert.NotEqual(IntPtr.Zero, animation.Handle);
} }
[SkippableFact] [SkippableTheory]
public void When_Default_TryCreate_From_NonSeekableStream() [MemberData(nameof(DefaultLottieFiles))]
public void When_Default_TryCreate_From_NonSeekableStream(string filename)
{ {
var path = Path.Combine(PathToImages, "LottieLogo1.json"); var path = Path.Combine(PathToImages, filename);
using var fileStream = File.OpenRead(path); using var fileStream = File.OpenRead(path);
using var nonseekable = new NonSeekableReadOnlyStream(fileStream); using var nonseekable = new NonSeekableReadOnlyStream(fileStream);
var result = Animation.TryCreate(nonseekable, out var animation); var result = Animation.TryCreate(nonseekable, out var animation);
@ -111,10 +138,11 @@ namespace SkiaSharp.Tests
Assert.NotEqual(IntPtr.Zero, animation?.Handle); Assert.NotEqual(IntPtr.Zero, animation?.Handle);
} }
[SkippableFact] [SkippableTheory]
public void When_Default_Create_From_NonSeekableStream() [MemberData(nameof(DefaultLottieFiles))]
public void When_Default_Create_From_NonSeekableStream(string filename)
{ {
var path = Path.Combine(PathToImages, "LottieLogo1.json"); var path = Path.Combine(PathToImages, filename);
using var fileStream = File.OpenRead(path); using var fileStream = File.OpenRead(path);
using var nonseekable = new NonSeekableReadOnlyStream(fileStream); using var nonseekable = new NonSeekableReadOnlyStream(fileStream);
var animation = Animation.Create(nonseekable); var animation = Animation.Create(nonseekable);
@ -123,20 +151,22 @@ namespace SkiaSharp.Tests
Assert.NotEqual(IntPtr.Zero, animation.Handle); Assert.NotEqual(IntPtr.Zero, animation.Handle);
} }
[SkippableFact] [SkippableTheory]
public void When_Default_TryCreate_From_File() [MemberData(nameof(DefaultLottieFiles))]
public void When_Default_TryCreate_From_File(string filename)
{ {
var path = Path.Combine(PathToImages, "LottieLogo1.json"); var path = Path.Combine(PathToImages, filename);
var result = Animation.TryCreate(path, out var animation); var result = Animation.TryCreate(path, out var animation);
Assert.True(result); Assert.True(result);
Assert.NotEqual(IntPtr.Zero, animation?.Handle); Assert.NotEqual(IntPtr.Zero, animation?.Handle);
} }
[SkippableFact] [SkippableTheory]
public void When_Default_Create_From_File() [MemberData(nameof(DefaultLottieFiles))]
public void When_Default_Create_From_File(string filename)
{ {
var path = Path.Combine(PathToImages, "LottieLogo1.json"); var path = Path.Combine(PathToImages, filename);
var animation = Animation.Create(path); var animation = Animation.Create(path);
Assert.NotNull(animation); Assert.NotNull(animation);