From 03c9b17bdffb14ad92ace5737dcac2eeeec13faa Mon Sep 17 00:00:00 2001 From: Jerome Laban Date: Tue, 10 Sep 2024 12:01:39 -0400 Subject: [PATCH] fix: Adjust skiasharp loading --- src/.nuspec/Uno.Resizetizer.targets | 63 +--- src/Resizetizer/src/SkiaSharpTools.cs | 448 ++++++++++++++++---------- 2 files changed, 278 insertions(+), 233 deletions(-) diff --git a/src/.nuspec/Uno.Resizetizer.targets b/src/.nuspec/Uno.Resizetizer.targets index 9cd40b93..d42356bd 100644 --- a/src/.nuspec/Uno.Resizetizer.targets +++ b/src/.nuspec/Uno.Resizetizer.targets @@ -9,7 +9,8 @@ - <_UnoResizetizerTaskAssemblyName>$(MSBuildThisFileDirectory)netstandard2.0\Uno.Resizetizer_v0.dll + <_UnoResizetizerTaskAssemblyName Condition=" '$(MSBuildRuntimeType)' != 'Core' ">$(MSBuildThisFileDirectory)netstandard2.0\Uno.Resizetizer_v0.dll + <_UnoResizetizerTaskAssemblyName Condition=" '$(MSBuildRuntimeType)' == 'Core' ">$(MSBuildThisFileDirectory)net6.0\Uno.Resizetizer_v0.dll + BeforeTargets="UnoResizetizeCollectItems"> - - - - <_ResizetizerRuntimeIdentifier>$(NETCoreSdkPortableRuntimeIdentifier) - - <_ResizetizerRuntimeIdentifier Condition=" $(_ResizetizerRuntimeIdentifier.Contains('osx')) ">osx - <_ResizetizerRuntimeIdentifierDirectory>$([MSBuild]::NormalizePath('$(MSBuildThisFileDirectory)', 'netstandard2.0', 'runtimes', '$(_ResizetizerRuntimeIdentifier)')) - <_ResizetizerRuntimeAssetsOutput>$([MSBuild]::NormalizePath('$(MSBuildThisFileDirectory)', 'netstandard2.0')) - - - - <_ResiztizerRuntimeAssets Include="$(_ResizetizerRuntimeIdentifierDirectory)\**\*" - OutputDirectory="$(_ResizetizerRuntimeAssetsOutput)" /> - - - - - - - - - - - - - diff --git a/src/Resizetizer/src/SkiaSharpTools.cs b/src/Resizetizer/src/SkiaSharpTools.cs index 0ba683a1..a5bd91e0 100644 --- a/src/Resizetizer/src/SkiaSharpTools.cs +++ b/src/Resizetizer/src/SkiaSharpTools.cs @@ -1,181 +1,283 @@ using System; using System.Diagnostics; using System.IO; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using SkiaSharp; namespace Uno.Resizetizer { - internal abstract class SkiaSharpTools - { - public static SkiaSharpTools Create(bool isVector, string filename, SKSize? baseSize, SKColor? backgroundColor, SKColor? tintColor, ILogger logger) - => isVector - ? new SkiaSharpSvgTools(filename, baseSize, backgroundColor, tintColor, logger) as SkiaSharpTools - : new SkiaSharpBitmapTools(filename, baseSize, backgroundColor, tintColor, logger); - - public static SkiaSharpTools CreateImaginary(SKColor? backgroundColor, ILogger logger) - => new SkiaSharpImaginaryTools(backgroundColor, logger); - - public SkiaSharpTools(ResizeImageInfo info, ILogger logger) - : this(info.Filename, info.BaseSize, info.Color, info.TintColor, logger) - { - } - - public SkiaSharpTools(string filename, SKSize? baseSize, SKColor? backgroundColor, SKColor? tintColor, ILogger logger) - { - Logger = logger; - Filename = filename; - BaseSize = baseSize; - BackgroundColor = backgroundColor; - - if (tintColor is SKColor tint) - { - Logger?.Log($"Detected a tint color of {tint}"); - - Paint = new SKPaint - { - ColorFilter = SKColorFilter.CreateBlendMode(tint, SKBlendMode.SrcIn) - }; - } - } - - public string Filename { get; } - - public SKSize? BaseSize { get; } - - public SKColor? BackgroundColor { get; } - - public ILogger Logger { get; } - - public SKPaint Paint { get; } - - public void Resize(DpiPath dpi, string destination, double additionalScale = 1.0, bool dpiSizeIsAbsolute = false) - { - var sw = new Stopwatch(); - sw.Start(); - - var originalSize = GetOriginalSize(); - var absoluteSize = dpiSizeIsAbsolute ? dpi.Size : null; - var (scaledSize, scale) = GetScaledSize(originalSize, dpi, absoluteSize); - var (canvasSize, _) = GetCanvasSize(dpi, null, this); - - using (var tempBitmap = new SKBitmap(canvasSize.Width, canvasSize.Height)) - { - Draw(tempBitmap, additionalScale, originalSize, scale, scaledSize); - Save(destination, tempBitmap); - } - - sw.Stop(); - Logger?.Log($"Save Image took {sw.ElapsedMilliseconds}ms ({destination})"); - } - - public static (SKSizeI Scaled, SKSize Unscaled) GetCanvasSize(DpiPath dpi, SKSize? baseSize = null, SkiaSharpTools baseTools = null) - { - // if an explicit size was given by the type of image, use that - if (dpi.Size is SKSize size) - { - var scale = (float)dpi.Scale; - var scaled = new SKSizeI( - (int)(size.Width * scale), - (int)(size.Height * scale)); - return (scaled, size); - } - - // if an explicit size was given in the csproj, use that - if (baseSize is SKSize bs) - { - var scale = (float)dpi.Scale; - var scaled = new SKSizeI( - (int)(bs.Width * scale), - (int)(bs.Height * scale)); - return (scaled, bs); - } - - // try determine the best size based on the loaded image - if (baseTools is not null) - { - var baseOriginalSize = baseTools.GetOriginalSize(); - var (baseScaledSize, _) = baseTools.GetScaledSize(baseOriginalSize, dpi.Scale); - return (baseScaledSize, baseOriginalSize); - } - - throw new InvalidOperationException("The canvas size cannot be calculated if there is no size to start from (DPI size, BaseSize or image size)."); - } - - void Draw(SKBitmap tempBitmap, double additionalScale, SKSize originalSize, float scale, SKSizeI scaledSize) - { - using var canvas = new SKCanvas(tempBitmap); - - var canvasSize = tempBitmap.Info.Size; - - // clear - canvas.Clear(BackgroundColor ?? SKColors.Transparent); - - // center the drawing - canvas.Translate( - (canvasSize.Width - scaledSize.Width) / 2, - (canvasSize.Height - scaledSize.Height) / 2); - - // apply initial scale to size the image to fit the canvas - canvas.Scale(scale, scale); - - // apply additional user scaling - if (additionalScale != 1.0) - { - var userFgScale = (float)additionalScale; - - // add the user scale to the main scale - scale *= userFgScale; - - // work out the center as if the canvas was exactly the same size as the foreground - var fgCenterX = originalSize.Width / 2; - var fgCenterY = originalSize.Height / 2; - - // scale to the user scale, centering - canvas.Scale(userFgScale, userFgScale, fgCenterX, fgCenterY); - } - - // draw - DrawUnscaled(canvas, scale); - } - - void Save(string destination, SKBitmap tempBitmap) - { - using var stream = File.Create(destination); - tempBitmap.Encode(stream, SKEncodedImageFormat.Png, 100); - } - - public abstract SKSize GetOriginalSize(); - - public abstract void DrawUnscaled(SKCanvas canvas, float scale); - - public (SKSizeI, float) GetScaledSize(SKSize originalSize, DpiPath dpi, SKSize? absoluteSize = null) => - GetScaledSize(originalSize, dpi.Scale, absoluteSize ?? dpi.Size); - - public (SKSizeI, float) GetScaledSize(SKSize originalSize, decimal resizeRatio, SKSize? absoluteSize = null) - { - var sourceNominalWidth = (int)(absoluteSize?.Width ?? BaseSize?.Width ?? originalSize.Width); - var sourceNominalHeight = (int)(absoluteSize?.Height ?? BaseSize?.Height ?? originalSize.Height); - - // Find the actual size of the image - var sourceActualWidth = (double)originalSize.Width; - var sourceActualHeight = (double)originalSize.Height; - - // Figure out what the ratio to convert the actual image size to the nominal size is - var nominalRatio = Math.Min( - sourceNominalWidth / sourceActualWidth, - sourceNominalHeight / sourceActualHeight); - - // Multiply nominal ratio by the resize ratio to get our final ratio we actually adjust by - var adjustRatio = nominalRatio * (double)resizeRatio; - - // Figure out our scaled width and height to make a new canvas for - var scaledWidth = sourceActualWidth * adjustRatio; - var scaledHeight = sourceActualHeight * adjustRatio; - var scaledSize = new SKSizeI( - (int)Math.Round(scaledWidth), - (int)Math.Round(scaledHeight)); - - return (scaledSize, (float)adjustRatio); - } - } + internal abstract class SkiaSharpTools + { + private delegate void SetDllImportResolverDelegate(Assembly assembly, Func resolver); + + private static bool _initialized; + + protected SkiaSharpTools() + { + Initialize(); + } + public static void Initialize() + { + if (!_initialized) + { + _initialized = true; + } + + // set a variable to hold the fact that we're running inside netcore + var isNetCore = Type.GetType("System.Runtime.Loader.AssemblyLoadContext") != null; + + if (isNetCore) + { + // Create a delegate method that will call NativeLibrary.SetDllImportResolver using reflection + var setDllImportResolver = (SetDllImportResolverDelegate)Type + .GetType("System.Runtime.InteropServices.NativeLibrary.SetDllImportResolver") + ?.GetMethod("SetDllImportResolver", new[] { typeof(Assembly), typeof(Func) }) + ?.CreateDelegate(typeof(SetDllImportResolverDelegate)); + + if (setDllImportResolver is not null) + { + setDllImportResolver(typeof(SkiaSharpTools).Assembly, ImportResolver); + + static IntPtr ImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) + { + IntPtr libHandle = IntPtr.Zero; + + if (libraryName == "libHarfBuzzSharp") + { + if (!NativeLibrary.TryLoad("libHarfBuzzSharp.dll", assembly, DllImportSearchPath.AssemblyDirectory, out libHandle)) + { + throw new InvalidOperationException($"Failed to load libHarfBuzzSharp"); + } + } + + if (libraryName == "libSkiaSharp") + { + if (!NativeLibrary.TryLoad("libSkiaSharp.dll", assembly, DllImportSearchPath.AssemblyDirectory, out libHandle)) + { + throw new InvalidOperationException($"Failed to load libSkiaSharp"); + } + } + + return libHandle; + } + } + else + { + throw new InvalidOperationException($"Unable to find System.Runtime.InteropServices.NativeLibrary.SetDllImportResolver"); + } + } + else { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + && typeof(SkiaSharpTools).Assembly.CodeBase is { } codebase + && Path.GetDirectoryName(codebase) is { } directory) + { + // create a variable that contains the current platform like `win-arm64` or `win-x64` or `win-x86` + var bitness = Environment.Is64BitProcess ? "x64" : "x86"; + var arch = RuntimeInformation.ProcessArchitecture == Architecture.Arm64 ? "arm64" : bitness; + + var runtimePath = Path.Combine(directory, "runtime", "win-" + arch); + + var libHarfBuzzSharp = Path.Combine(runtimePath, "libHarfBuzzSharp.dll"); + var libSkiaSharp = Path.Combine(runtimePath, "libSkiaSharp.dll"); + + if (Directory.Exists(libHarfBuzzSharp) + && Directory.Exists(libSkiaSharp)) + { + var r1 = LoadLibrary(libHarfBuzzSharp); + + if (r1 == IntPtr.Zero) + { + throw new InvalidOperationException($"Failed to load {libHarfBuzzSharp}"); + } + + var r2 = LoadLibrary(libSkiaSharp); + + if (r2 == IntPtr.Zero) + { + throw new InvalidOperationException($"Failed to load {libSkiaSharp}"); + } + } + } + } + } + +#if !NET6_0_OR_GREATER + // Declare the dllimport for LoadLibrary + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr LoadLibrary(string dllToLoad); +#endif + + public static SkiaSharpTools Create(bool isVector, string filename, SKSize? baseSize, SKColor? backgroundColor, SKColor? tintColor, ILogger logger) + => isVector + ? new SkiaSharpSvgTools(filename, baseSize, backgroundColor, tintColor, logger) as SkiaSharpTools + : new SkiaSharpBitmapTools(filename, baseSize, backgroundColor, tintColor, logger); + + public static SkiaSharpTools CreateImaginary(SKColor? backgroundColor, ILogger logger) + => new SkiaSharpImaginaryTools(backgroundColor, logger); + + public SkiaSharpTools(ResizeImageInfo info, ILogger logger) + : this(info.Filename, info.BaseSize, info.Color, info.TintColor, logger) + { + } + + public SkiaSharpTools(string filename, SKSize? baseSize, SKColor? backgroundColor, SKColor? tintColor, ILogger logger) + { + Logger = logger; + Filename = filename; + BaseSize = baseSize; + BackgroundColor = backgroundColor; + + if (tintColor is SKColor tint) + { + Logger?.Log($"Detected a tint color of {tint}"); + + Paint = new SKPaint + { + ColorFilter = SKColorFilter.CreateBlendMode(tint, SKBlendMode.SrcIn) + }; + } + } + + public string Filename { get; } + + public SKSize? BaseSize { get; } + + public SKColor? BackgroundColor { get; } + + public ILogger Logger { get; } + + public SKPaint Paint { get; } + + public void Resize(DpiPath dpi, string destination, double additionalScale = 1.0, bool dpiSizeIsAbsolute = false) + { + var sw = new Stopwatch(); + sw.Start(); + + var originalSize = GetOriginalSize(); + var absoluteSize = dpiSizeIsAbsolute ? dpi.Size : null; + var (scaledSize, scale) = GetScaledSize(originalSize, dpi, absoluteSize); + var (canvasSize, _) = GetCanvasSize(dpi, null, this); + + using (var tempBitmap = new SKBitmap(canvasSize.Width, canvasSize.Height)) + { + Draw(tempBitmap, additionalScale, originalSize, scale, scaledSize); + Save(destination, tempBitmap); + } + + sw.Stop(); + Logger?.Log($"Save Image took {sw.ElapsedMilliseconds}ms ({destination})"); + } + + public static (SKSizeI Scaled, SKSize Unscaled) GetCanvasSize(DpiPath dpi, SKSize? baseSize = null, SkiaSharpTools baseTools = null) + { + // if an explicit size was given by the type of image, use that + if (dpi.Size is SKSize size) + { + var scale = (float)dpi.Scale; + var scaled = new SKSizeI( + (int)(size.Width * scale), + (int)(size.Height * scale)); + return (scaled, size); + } + + // if an explicit size was given in the csproj, use that + if (baseSize is SKSize bs) + { + var scale = (float)dpi.Scale; + var scaled = new SKSizeI( + (int)(bs.Width * scale), + (int)(bs.Height * scale)); + return (scaled, bs); + } + + // try determine the best size based on the loaded image + if (baseTools is not null) + { + var baseOriginalSize = baseTools.GetOriginalSize(); + var (baseScaledSize, _) = baseTools.GetScaledSize(baseOriginalSize, dpi.Scale); + return (baseScaledSize, baseOriginalSize); + } + + throw new InvalidOperationException("The canvas size cannot be calculated if there is no size to start from (DPI size, BaseSize or image size)."); + } + + void Draw(SKBitmap tempBitmap, double additionalScale, SKSize originalSize, float scale, SKSizeI scaledSize) + { + using var canvas = new SKCanvas(tempBitmap); + + var canvasSize = tempBitmap.Info.Size; + + // clear + canvas.Clear(BackgroundColor ?? SKColors.Transparent); + + // center the drawing + canvas.Translate( + (canvasSize.Width - scaledSize.Width) / 2, + (canvasSize.Height - scaledSize.Height) / 2); + + // apply initial scale to size the image to fit the canvas + canvas.Scale(scale, scale); + + // apply additional user scaling + if (additionalScale != 1.0) + { + var userFgScale = (float)additionalScale; + + // add the user scale to the main scale + scale *= userFgScale; + + // work out the center as if the canvas was exactly the same size as the foreground + var fgCenterX = originalSize.Width / 2; + var fgCenterY = originalSize.Height / 2; + + // scale to the user scale, centering + canvas.Scale(userFgScale, userFgScale, fgCenterX, fgCenterY); + } + + // draw + DrawUnscaled(canvas, scale); + } + + void Save(string destination, SKBitmap tempBitmap) + { + using var stream = File.Create(destination); + tempBitmap.Encode(stream, SKEncodedImageFormat.Png, 100); + } + + public abstract SKSize GetOriginalSize(); + + public abstract void DrawUnscaled(SKCanvas canvas, float scale); + + public (SKSizeI, float) GetScaledSize(SKSize originalSize, DpiPath dpi, SKSize? absoluteSize = null) => + GetScaledSize(originalSize, dpi.Scale, absoluteSize ?? dpi.Size); + + public (SKSizeI, float) GetScaledSize(SKSize originalSize, decimal resizeRatio, SKSize? absoluteSize = null) + { + var sourceNominalWidth = (int)(absoluteSize?.Width ?? BaseSize?.Width ?? originalSize.Width); + var sourceNominalHeight = (int)(absoluteSize?.Height ?? BaseSize?.Height ?? originalSize.Height); + + // Find the actual size of the image + var sourceActualWidth = (double)originalSize.Width; + var sourceActualHeight = (double)originalSize.Height; + + // Figure out what the ratio to convert the actual image size to the nominal size is + var nominalRatio = Math.Min( + sourceNominalWidth / sourceActualWidth, + sourceNominalHeight / sourceActualHeight); + + // Multiply nominal ratio by the resize ratio to get our final ratio we actually adjust by + var adjustRatio = nominalRatio * (double)resizeRatio; + + // Figure out our scaled width and height to make a new canvas for + var scaledWidth = sourceActualWidth * adjustRatio; + var scaledHeight = sourceActualHeight * adjustRatio; + var scaledSize = new SKSizeI( + (int)Math.Round(scaledWidth), + (int)Math.Round(scaledHeight)); + + return (scaledSize, (float)adjustRatio); + } + } }