diff --git a/src/Skia/Avalonia.Skia/Avalonia.Skia.csproj b/src/Skia/Avalonia.Skia/Avalonia.Skia.csproj index 96fb0e5132..442d87cabb 100644 --- a/src/Skia/Avalonia.Skia/Avalonia.Skia.csproj +++ b/src/Skia/Avalonia.Skia/Avalonia.Skia.csproj @@ -1,6 +1,6 @@  - $(AvsLegacyTargetFrameworks);netstandard2.0 + $(AvsCurrentTargetFramework);$(AvsLegacyTargetFrameworks);netstandard2.0 true true true diff --git a/src/Skia/Avalonia.Skia/Compatibility/SkiaCompat.SKCanvas.cs b/src/Skia/Avalonia.Skia/Compatibility/SkiaCompat.SKCanvas.cs new file mode 100644 index 0000000000..ec6e562de2 --- /dev/null +++ b/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 +} diff --git a/src/Skia/Avalonia.Skia/Compatibility/SkiaCompat.SKImageFilter.cs b/src/Skia/Avalonia.Skia/Compatibility/SkiaCompat.SKImageFilter.cs new file mode 100644 index 0000000000..3b9f356f58 --- /dev/null +++ b/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 +} diff --git a/src/Skia/Avalonia.Skia/Compatibility/SkiaCompat.SKPath.cs b/src/Skia/Avalonia.Skia/Compatibility/SkiaCompat.SKPath.cs new file mode 100644 index 0000000000..dbb237bad7 --- /dev/null +++ b/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 +} diff --git a/src/Skia/Avalonia.Skia/Compatibility/SkiaCompat.cs b/src/Skia/Avalonia.Skia/Compatibility/SkiaCompat.cs new file mode 100644 index 0000000000..d093d2133e --- /dev/null +++ b/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 +} diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.Effects.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.Effects.cs index babc547209..d9c2c27496 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.Effects.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.Effects.cs @@ -30,7 +30,7 @@ partial class DrawingContextImpl if (blur.Radius <= 0) return null; var sigma = SkBlurRadiusToSigma(blur.Radius); - return SKImageFilter.CreateBlur(sigma, sigma); + return SkiaCompat.CreateBlur(sigma, sigma); } if (effect is IDropShadowEffect drop) @@ -41,7 +41,7 @@ partial class DrawingContextImpl alpha *= _currentOpacity; 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; diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index be53f21af4..2fdb523287 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -139,7 +139,7 @@ namespace Avalonia.Skia { if (!_isDisposed) { - _context.Canvas.SetMatrix(_revertTransform); + SkiaCompat.SetMatrix(_context.Canvas, _revertTransform); _context._leased = false; _isDisposed = true; } @@ -328,7 +328,7 @@ namespace Avalonia.Skia { 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)); paint.Reset(); @@ -853,7 +853,7 @@ namespace Avalonia.Skia transform *= _postTransform.Value; } - Canvas.SetMatrix(transform.ToSKMatrix()); + SkiaCompat.SetMatrix(Canvas, transform.ToSKMatrix()); } } diff --git a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs index 9b5d104aad..01b62dfe49 100644 --- a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs @@ -157,7 +157,7 @@ namespace Avalonia.Skia var oldMatrix = context.Canvas.TotalMatrix; context.Canvas.ResetMatrix(); _surface.Surface.Draw(context.Canvas, 0, 0, null); - context.Canvas.SetMatrix(oldMatrix); + SkiaCompat.SetMatrix(context.Canvas, oldMatrix); } } diff --git a/src/Skia/Avalonia.Skia/TransformedGeometryImpl.cs b/src/Skia/Avalonia.Skia/TransformedGeometryImpl.cs index 2dee8c318c..5ad0cfdcb5 100644 --- a/src/Skia/Avalonia.Skia/TransformedGeometryImpl.cs +++ b/src/Skia/Avalonia.Skia/TransformedGeometryImpl.cs @@ -20,8 +20,9 @@ namespace Avalonia.Skia var matrix = transform.ToSKMatrix(); var transformedPath = StrokePath = source.StrokePath.Clone(); - transformedPath?.Transform(matrix); - + if (transformedPath is not null) + SkiaCompat.Transform(transformedPath, matrix); + Bounds = transformedPath?.TightBounds.ToAvaloniaRect() ?? default; if (ReferenceEquals(source.StrokePath, source.FillPath)) @@ -29,7 +30,7 @@ namespace Avalonia.Skia else if (source.FillPath != null) { FillPath = transformedPath = source.FillPath.Clone(); - transformedPath.Transform(matrix); + SkiaCompat.Transform(transformedPath, matrix); } }