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:
Родитель
0f250519aa
Коммит
8f81be2691
|
@ -103,7 +103,7 @@ namespace SkiaSharp
|
|||
if (stream == null)
|
||||
throw new ArgumentNullException (nameof (stream));
|
||||
if (stream.CanSeek) {
|
||||
return Create (stream, stream.Length);
|
||||
return Create (stream, stream.Length - stream.Position);
|
||||
} else {
|
||||
using var memory = new SKDynamicMemoryWStream ();
|
||||
using (var managed = new SKManagedStream (stream)) {
|
||||
|
|
|
@ -12,6 +12,25 @@ namespace SkiaSharp
|
|||
{
|
||||
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) =>
|
||||
new Span<byte> ((void*)ptr, size);
|
||||
|
||||
|
@ -105,8 +124,7 @@ namespace SkiaSharp
|
|||
// GetUnicodeStringLength
|
||||
|
||||
private static int GetUnicodeStringLength (SKTextEncoding encoding) =>
|
||||
encoding switch
|
||||
{
|
||||
encoding switch {
|
||||
SKTextEncoding.Utf8 => 1,
|
||||
SKTextEncoding.Utf16 => 1,
|
||||
SKTextEncoding.Utf32 => 2,
|
||||
|
@ -116,8 +134,7 @@ namespace SkiaSharp
|
|||
// GetCharacterByteSize
|
||||
|
||||
internal static int GetCharacterByteSize (this SKTextEncoding encoding) =>
|
||||
encoding switch
|
||||
{
|
||||
encoding switch {
|
||||
SKTextEncoding.Utf8 => 1,
|
||||
SKTextEncoding.Utf16 => 2,
|
||||
SKTextEncoding.Utf32 => 4,
|
||||
|
@ -156,8 +173,7 @@ namespace SkiaSharp
|
|||
}
|
||||
|
||||
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),
|
||||
|
@ -177,8 +193,7 @@ namespace SkiaSharp
|
|||
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),
|
||||
|
@ -197,8 +212,7 @@ namespace SkiaSharp
|
|||
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),
|
||||
|
|
|
@ -21,6 +21,8 @@ namespace SkiaSharp.Skottie
|
|||
protected override void DisposeNative ()
|
||||
=> SkottieApi.skottie_animation_delete (Handle);
|
||||
|
||||
// Parse
|
||||
|
||||
public static Animation? Parse (string json) =>
|
||||
TryParse (json, out var animation)
|
||||
? animation
|
||||
|
@ -34,6 +36,8 @@ namespace SkiaSharp.Skottie
|
|||
return animation != null;
|
||||
}
|
||||
|
||||
// Create
|
||||
|
||||
public static Animation? Create (Stream stream) =>
|
||||
TryCreate (stream, out var animation)
|
||||
? animation
|
||||
|
@ -56,8 +60,8 @@ namespace SkiaSharp.Skottie
|
|||
{
|
||||
_ = 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);
|
||||
}
|
||||
|
||||
public static Animation? Create (SKData data) =>
|
||||
|
@ -69,8 +73,13 @@ namespace SkiaSharp.Skottie
|
|||
{
|
||||
_ = 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) =>
|
||||
|
@ -82,16 +91,20 @@ namespace SkiaSharp.Skottie
|
|||
{
|
||||
_ = 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);
|
||||
|
||||
|
@ -104,6 +117,8 @@ namespace SkiaSharp.Skottie
|
|||
public void SeekFrameTime (TimeSpan time, InvalidationController? ic = null)
|
||||
=> SeekFrameTime (time.TotalSeconds, ic);
|
||||
|
||||
// Properties
|
||||
|
||||
public TimeSpan Duration
|
||||
=> TimeSpan.FromSeconds (SkottieApi.skottie_animation_get_duration (Handle));
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -179,6 +179,32 @@ namespace SkiaSharp.Tests
|
|||
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.")]
|
||||
public void DataDisposedReturnsInvalidStream()
|
||||
{
|
||||
|
|
|
@ -8,10 +8,29 @@ namespace SkiaSharp.Tests
|
|||
{
|
||||
public class AnimationTest : SKTest
|
||||
{
|
||||
[SkippableFact]
|
||||
public void When_Default_TryParse()
|
||||
public static TheoryData<string> DefaultLottieFiles =>
|
||||
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);
|
||||
|
||||
Assert.True(result);
|
||||
|
@ -19,20 +38,22 @@ namespace SkiaSharp.Tests
|
|||
Assert.NotEqual(IntPtr.Zero, animation.Handle);
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
public void When_Default_Parse()
|
||||
[SkippableTheory]
|
||||
[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));
|
||||
|
||||
Assert.NotNull(animation);
|
||||
Assert.NotEqual(IntPtr.Zero, animation.Handle);
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
public void When_Default_TryCreate_From_SKData()
|
||||
[SkippableTheory]
|
||||
[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);
|
||||
var result = Animation.TryCreate(data, out var animation);
|
||||
|
||||
|
@ -41,10 +62,11 @@ namespace SkiaSharp.Tests
|
|||
Assert.NotEqual(IntPtr.Zero, animation.Handle);
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
public void When_Default_Create_From_SKData()
|
||||
[SkippableTheory]
|
||||
[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);
|
||||
var animation = Animation.Create(data);
|
||||
|
||||
|
@ -52,10 +74,11 @@ namespace SkiaSharp.Tests
|
|||
Assert.NotEqual(IntPtr.Zero, animation.Handle);
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
public void When_Default_TryCreate_From_SKStream()
|
||||
[SkippableTheory]
|
||||
[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 managedStream = new SKManagedStream(fileStream);
|
||||
var result = Animation.TryCreate(managedStream, out var animation);
|
||||
|
@ -65,10 +88,11 @@ namespace SkiaSharp.Tests
|
|||
Assert.NotEqual(IntPtr.Zero, animation.Handle);
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
public void When_Default_Create_From_SKStream()
|
||||
[SkippableTheory]
|
||||
[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 managedStream = new SKManagedStream(fileStream);
|
||||
var animation = Animation.Create(managedStream);
|
||||
|
@ -77,10 +101,11 @@ namespace SkiaSharp.Tests
|
|||
Assert.NotEqual(IntPtr.Zero, animation.Handle);
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
public void When_Default_TryCreate_From_Stream()
|
||||
[SkippableTheory]
|
||||
[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);
|
||||
var result = Animation.TryCreate(fileStream, out var animation);
|
||||
|
||||
|
@ -88,10 +113,11 @@ namespace SkiaSharp.Tests
|
|||
Assert.NotEqual(IntPtr.Zero, animation?.Handle);
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
public void When_Default_Create_From_Stream()
|
||||
[SkippableTheory]
|
||||
[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);
|
||||
var animation = Animation.Create(fileStream);
|
||||
|
||||
|
@ -99,10 +125,11 @@ namespace SkiaSharp.Tests
|
|||
Assert.NotEqual(IntPtr.Zero, animation.Handle);
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
public void When_Default_TryCreate_From_NonSeekableStream()
|
||||
[SkippableTheory]
|
||||
[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 nonseekable = new NonSeekableReadOnlyStream(fileStream);
|
||||
var result = Animation.TryCreate(nonseekable, out var animation);
|
||||
|
@ -111,10 +138,11 @@ namespace SkiaSharp.Tests
|
|||
Assert.NotEqual(IntPtr.Zero, animation?.Handle);
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
public void When_Default_Create_From_NonSeekableStream()
|
||||
[SkippableTheory]
|
||||
[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 nonseekable = new NonSeekableReadOnlyStream(fileStream);
|
||||
var animation = Animation.Create(nonseekable);
|
||||
|
@ -123,20 +151,22 @@ namespace SkiaSharp.Tests
|
|||
Assert.NotEqual(IntPtr.Zero, animation.Handle);
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
public void When_Default_TryCreate_From_File()
|
||||
[SkippableTheory]
|
||||
[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);
|
||||
|
||||
Assert.True(result);
|
||||
Assert.NotEqual(IntPtr.Zero, animation?.Handle);
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
public void When_Default_Create_From_File()
|
||||
[SkippableTheory]
|
||||
[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);
|
||||
|
||||
Assert.NotNull(animation);
|
||||
|
|
Загрузка…
Ссылка в новой задаче