Browse Source

Make Avalonia.Skia compatible with both SkiaSharp 2.88 and 3.0 (#14510)

* Add missing AvsCurrentTargetFramework on Skia project

* Implement SkiaCompat using UnsafeAccessor and cached functon pointer

* Do not support SkiaSharp 3.0 on older targest, avoiding reflection completely

* Make SkiaCompat static initialization more consistent

* Remove function pointers completely

---------

Co-authored-by: Nikita Tsukanov <keks9n@gmail.com>
pull/14856/head
Max Katz 3 years ago
committed by GitHub
parent
commit
11f5203ab2
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      src/Skia/Avalonia.Skia/Avalonia.Skia.csproj
  2. 34
      src/Skia/Avalonia.Skia/Compatibility/SkiaCompat.SKCanvas.cs
  3. 57
      src/Skia/Avalonia.Skia/Compatibility/SkiaCompat.SKImageFilter.cs
  4. 32
      src/Skia/Avalonia.Skia/Compatibility/SkiaCompat.SKPath.cs
  5. 16
      src/Skia/Avalonia.Skia/Compatibility/SkiaCompat.cs
  6. 4
      src/Skia/Avalonia.Skia/DrawingContextImpl.Effects.cs
  7. 6
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  8. 2
      src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs
  9. 7
      src/Skia/Avalonia.Skia/TransformedGeometryImpl.cs

2
src/Skia/Avalonia.Skia/Avalonia.Skia.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>$(AvsLegacyTargetFrameworks);netstandard2.0</TargetFrameworks> <TargetFrameworks>$(AvsCurrentTargetFramework);$(AvsLegacyTargetFrameworks);netstandard2.0</TargetFrameworks>
<IncludeLinuxSkia>true</IncludeLinuxSkia> <IncludeLinuxSkia>true</IncludeLinuxSkia>
<IncludeWasmSkia>true</IncludeWasmSkia> <IncludeWasmSkia>true</IncludeWasmSkia>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>

34
src/Skia/Avalonia.Skia/Compatibility/SkiaCompat.SKCanvas.cs

@ -0,0 +1,34 @@
using System;
using System.Runtime.CompilerServices;
using SkiaSharp;
namespace Avalonia.Skia;
internal static partial class SkiaCompat
{
public static void SetMatrix(SKCanvas canvas, in SKMatrix matrix)
{
if (s_isSkiaSharp3)
{
#if NET8_0_OR_GREATER
NewCanvasSetMatrix(canvas, matrix);
#else
throw UnsupportedException();
#endif
}
else
{
LegacyCall(canvas, matrix);
static void LegacyCall(SKCanvas canvas, in SKMatrix matrix)
{
canvas.SetMatrix(matrix);
}
}
}
#if NET8_0_OR_GREATER
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "SetMatrix")]
private static extern void NewCanvasSetMatrix(SKCanvas canvas, in SKMatrix matrix);
#endif
}

57
src/Skia/Avalonia.Skia/Compatibility/SkiaCompat.SKImageFilter.cs

@ -0,0 +1,57 @@
using System;
using System.Runtime.CompilerServices;
using SkiaSharp;
namespace Avalonia.Skia;
internal static partial class SkiaCompat
{
public static SKImageFilter CreateBlur(float sigmaX, float sigmaY)
{
if (s_isSkiaSharp3)
{
#if NET8_0_OR_GREATER
return NewSKImageFilterCreateBlur(null, sigmaX, sigmaY);
#else
throw UnsupportedException();
#endif
}
else
{
return LegacyBlurCall(sigmaX, sigmaY);
static SKImageFilter LegacyBlurCall(float sigmaX, float sigmaY) =>
SKImageFilter.CreateBlur(sigmaX, sigmaY);
}
}
public static SKImageFilter CreateDropShadow(float dropOffsetX, float dropOffsetY, float sigma, float f,
SKColor color)
{
if (s_isSkiaSharp3)
{
#if NET8_0_OR_GREATER
return NewSKImageFilterCreateDropShadow(null!, dropOffsetX, dropOffsetY, sigma, f, color);
#else
throw UnsupportedException();
#endif
}
else
{
return LegacyDropShadowCall(dropOffsetX, dropOffsetY, sigma, f, color);
static SKImageFilter LegacyDropShadowCall(float dropOffsetX, float dropOffsetY, float sigma, float f, SKColor color) =>
SKImageFilter.CreateDropShadow(dropOffsetX, dropOffsetY, sigma, f, color);
}
}
#if NET8_0_OR_GREATER
// See https://github.com/dotnet/runtime/issues/90081 why we need `SKImageFilter _`
[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "CreateBlur")]
private static extern SKImageFilter NewSKImageFilterCreateBlur(SKImageFilter? _, float sigmaX, float sigmaY);
[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "CreateDropShadow")]
private static extern SKImageFilter NewSKImageFilterCreateDropShadow(SKImageFilter? _, float dropOffsetX,
float dropOffsetY, float sigma, float f, SKColor color);
#endif
}

32
src/Skia/Avalonia.Skia/Compatibility/SkiaCompat.SKPath.cs

@ -0,0 +1,32 @@
using System;
using System.Runtime.CompilerServices;
using SkiaSharp;
namespace Avalonia.Skia;
internal static partial class SkiaCompat
{
public static void Transform(SKPath path, in SKMatrix matrix)
{
if (s_isSkiaSharp3)
{
#if NET8_0_OR_GREATER
NewPathTransform(path, matrix);
#else
throw UnsupportedException();
#endif
}
else
{
LegacyCall(path, matrix);
static void LegacyCall(SKPath path, in SKMatrix matrix) =>
path.Transform(matrix);
}
}
#if NET8_0_OR_GREATER
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "Transform")]
private static extern void NewPathTransform(SKPath path, in SKMatrix matrix);
#endif
}

16
src/Skia/Avalonia.Skia/Compatibility/SkiaCompat.cs

@ -0,0 +1,16 @@
using System;
using SkiaSharp;
namespace Avalonia.Skia;
internal static partial class SkiaCompat
{
private static readonly bool s_isSkiaSharp3 = typeof(SKPaint).Assembly.GetName().Version?.Major == 3;
#if !NET8_0_OR_GREATER
private static Exception UnsupportedException()
{
return new InvalidOperationException("Avalonia doesn't support SkiaSharp 3.0 on .NET 7 and older. Please upgrade to .NET 8.");
}
#endif
}

4
src/Skia/Avalonia.Skia/DrawingContextImpl.Effects.cs

@ -30,7 +30,7 @@ partial class DrawingContextImpl
if (blur.Radius <= 0) if (blur.Radius <= 0)
return null; return null;
var sigma = SkBlurRadiusToSigma(blur.Radius); var sigma = SkBlurRadiusToSigma(blur.Radius);
return SKImageFilter.CreateBlur(sigma, sigma); return SkiaCompat.CreateBlur(sigma, sigma);
} }
if (effect is IDropShadowEffect drop) if (effect is IDropShadowEffect drop)
@ -41,7 +41,7 @@ partial class DrawingContextImpl
alpha *= _currentOpacity; alpha *= _currentOpacity;
var color = new SKColor(drop.Color.R, drop.Color.G, drop.Color.B, (byte)Math.Max(0, Math.Min(255, alpha))); var color = new SKColor(drop.Color.R, drop.Color.G, drop.Color.B, (byte)Math.Max(0, Math.Min(255, alpha)));
return SKImageFilter.CreateDropShadow((float)drop.OffsetX, (float)drop.OffsetY, sigma, sigma, color); return SkiaCompat.CreateDropShadow((float)drop.OffsetX, (float)drop.OffsetY, sigma, sigma, color);
} }
return null; return null;

6
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -139,7 +139,7 @@ namespace Avalonia.Skia
{ {
if (!_isDisposed) if (!_isDisposed)
{ {
_context.Canvas.SetMatrix(_revertTransform); SkiaCompat.SetMatrix(_context.Canvas, _revertTransform);
_context._leased = false; _context._leased = false;
_isDisposed = true; _isDisposed = true;
} }
@ -328,7 +328,7 @@ namespace Avalonia.Skia
{ {
var ac = shadow.Color; var ac = shadow.Color;
var filter = SKImageFilter.CreateBlur(SkBlurRadiusToSigma(shadow.Blur), SkBlurRadiusToSigma(shadow.Blur)); var filter = SkiaCompat.CreateBlur(SkBlurRadiusToSigma(shadow.Blur), SkBlurRadiusToSigma(shadow.Blur));
var color = new SKColor(ac.R, ac.G, ac.B, (byte)(ac.A * opacity)); var color = new SKColor(ac.R, ac.G, ac.B, (byte)(ac.A * opacity));
paint.Reset(); paint.Reset();
@ -853,7 +853,7 @@ namespace Avalonia.Skia
transform *= _postTransform.Value; transform *= _postTransform.Value;
} }
Canvas.SetMatrix(transform.ToSKMatrix()); SkiaCompat.SetMatrix(Canvas, transform.ToSKMatrix());
} }
} }

2
src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs

@ -157,7 +157,7 @@ namespace Avalonia.Skia
var oldMatrix = context.Canvas.TotalMatrix; var oldMatrix = context.Canvas.TotalMatrix;
context.Canvas.ResetMatrix(); context.Canvas.ResetMatrix();
_surface.Surface.Draw(context.Canvas, 0, 0, null); _surface.Surface.Draw(context.Canvas, 0, 0, null);
context.Canvas.SetMatrix(oldMatrix); SkiaCompat.SetMatrix(context.Canvas, oldMatrix);
} }
} }

7
src/Skia/Avalonia.Skia/TransformedGeometryImpl.cs

@ -20,8 +20,9 @@ namespace Avalonia.Skia
var matrix = transform.ToSKMatrix(); var matrix = transform.ToSKMatrix();
var transformedPath = StrokePath = source.StrokePath.Clone(); var transformedPath = StrokePath = source.StrokePath.Clone();
transformedPath?.Transform(matrix); if (transformedPath is not null)
SkiaCompat.Transform(transformedPath, matrix);
Bounds = transformedPath?.TightBounds.ToAvaloniaRect() ?? default; Bounds = transformedPath?.TightBounds.ToAvaloniaRect() ?? default;
if (ReferenceEquals(source.StrokePath, source.FillPath)) if (ReferenceEquals(source.StrokePath, source.FillPath))
@ -29,7 +30,7 @@ namespace Avalonia.Skia
else if (source.FillPath != null) else if (source.FillPath != null)
{ {
FillPath = transformedPath = source.FillPath.Clone(); FillPath = transformedPath = source.FillPath.Clone();
transformedPath.Transform(matrix); SkiaCompat.Transform(transformedPath, matrix);
} }
} }

Loading…
Cancel
Save