From 5c80e05671ad5b1cd4a068d91a8452f9e715994f Mon Sep 17 00:00:00 2001 From: Michael Stonis Date: Mon, 20 Jan 2025 21:33:24 -0600 Subject: [PATCH] Add SVG icon support and clean up color handling - Added a new toolbar item with an SVG icon. - Introduced a new SVG file for the "more" icon. - Updated methods to accept nullable color parameters for better flexibility. - Cleaned up code by removing unused XML imports and optimizing variable usage. - Enhanced image caching logic for iOS and MacCatalyst platforms. --- AuroraControls.TestApp/MainPage.cs | 4 + AuroraControls.TestApp/Resources/SVG/more.svg | 11 ++ AuroraControlsMaui/IIconCache.cs | 16 +-- AuroraControlsMaui/IconCacheBase.cs | 110 ++++++++++-------- 4 files changed, 86 insertions(+), 55 deletions(-) create mode 100644 AuroraControls.TestApp/Resources/SVG/more.svg diff --git a/AuroraControls.TestApp/MainPage.cs b/AuroraControls.TestApp/MainPage.cs index 5a4024c..9c1e040 100644 --- a/AuroraControls.TestApp/MainPage.cs +++ b/AuroraControls.TestApp/MainPage.cs @@ -62,6 +62,10 @@ public MainPage(ILogger logger) ViewModel = new TestRxViewModel(); MvvmToolkitViewModel = new TestMvvmToolkitViewModel(); + this.ToolbarItems.Add( + new ToolbarItem() + .SetSvgIcon("more.svg")); + Content = new ScrollView { diff --git a/AuroraControls.TestApp/Resources/SVG/more.svg b/AuroraControls.TestApp/Resources/SVG/more.svg new file mode 100644 index 0000000..2b3910d --- /dev/null +++ b/AuroraControls.TestApp/Resources/SVG/more.svg @@ -0,0 +1,11 @@ + + + Shape + + + + + + + + \ No newline at end of file diff --git a/AuroraControlsMaui/IIconCache.cs b/AuroraControlsMaui/IIconCache.cs index 935e17f..e90d62a 100644 --- a/AuroraControlsMaui/IIconCache.cs +++ b/AuroraControlsMaui/IIconCache.cs @@ -15,7 +15,7 @@ public interface IIconCache /// The square size of the icon. /// Allows for setting an addiitonal cache key. /// Allows for setting the color of the icon. - Task IconFromSvg(string svgName, double squareSize = 22d, string additionalCacheKey = "", Color colorOverride = default(Color)); + Task IconFromSvg(string svgName, double squareSize = 22d, string additionalCacheKey = "", Color? colorOverride = default(Color)); /// /// Fetches an SVG icon by name. @@ -25,7 +25,7 @@ public interface IIconCache /// A Xamarin.Forms.Size representing the desired size of the icon. /// Allows for setting an addiitonal cache key. /// Allows for setting the color of the icon. - Task IconFromSvg(string svgName, Size size, string additionalCacheKey = "", Color colorOverride = default(Color)); + Task IconFromSvg(string svgName, Size size, string additionalCacheKey = "", Color? colorOverride = default(Color)); /// /// Fetches an SVG source by name. @@ -35,7 +35,7 @@ public interface IIconCache /// The square size of the icon. /// Allows for setting an addiitonal cache key. /// Allows for setting the color of the icon. - Task SourceFromSvg(string svgName, double squareSize = 22d, string additionalCacheKey = "", Color colorOverride = default(Color)); + Task SourceFromSvg(string svgName, double squareSize = 22d, string additionalCacheKey = "", Color? colorOverride = default(Color)); /// /// Fetches an SVG icon by name. @@ -45,7 +45,7 @@ public interface IIconCache /// A Xamarin.Forms.Size representing the desired size of the icon. /// Allows for setting an addiitonal cache key. /// Allows for setting the color of the icon. - Task SourceFromSvg(string svgName, Size size, string additionalCacheKey = "", Color colorOverride = default(Color)); + Task SourceFromSvg(string svgName, Size size, string additionalCacheKey = "", Color? colorOverride = default(Color)); /// /// Fetches an SVG icon by name. @@ -56,7 +56,7 @@ public interface IIconCache /// A double representing the desired size of the icon. /// Allows for setting an addiitonal cache key. /// Allows for setting the color of the icon. - Task SourceFromRawSvg(string svgName, string svgValue, double squareSize = 22d, string additionalCacheKey = "", Color colorOverride = default(Color)); + Task SourceFromRawSvg(string svgName, string svgValue, double squareSize = 22d, string additionalCacheKey = "", Color? colorOverride = default(Color)); /// /// Fetches an SVG icon by name. @@ -67,7 +67,7 @@ public interface IIconCache /// A Xamarin.Forms.Size representing the desired size of the icon. /// Allows for setting an addiitonal cache key. /// Allows for setting the color of the icon. - Task SourceFromRawSvg(string svgName, string svgValue, Size size, string additionalCacheKey = "", Color colorOverride = default(Color)); + Task SourceFromRawSvg(string svgName, string svgValue, Size size, string additionalCacheKey = "", Color? colorOverride = default(Color)); /// /// Fetches an SVG file image source by name. @@ -77,7 +77,7 @@ public interface IIconCache /// The square size of the icon. /// Allows for setting an addiitonal cache key. /// Allows for setting the color of the icon. - Task FileImageSourceFromSvg(string svgName, double squareSize = 22d, string additionalCacheKey = "", Color colorOverride = default(Color)); + Task FileImageSourceFromSvg(string svgName, double squareSize = 22d, string additionalCacheKey = "", Color? colorOverride = default(Color)); /// /// Fetches an SVG icon by name. @@ -87,7 +87,7 @@ public interface IIconCache /// A Xamarin.Forms.Size representing the desired size of the icon. /// Allows for setting an addiitonal cache key. /// Allows for setting the color of the icon. - Task FileImageSourceFromSvg(string svgName, Size size, string additionalCacheKey = "", Color colorOverride = default(Color)); + Task FileImageSourceFromSvg(string svgName, Size size, string additionalCacheKey = "", Color? colorOverride = default(Color)); /// /// Loads the assembly. diff --git a/AuroraControlsMaui/IconCacheBase.cs b/AuroraControlsMaui/IconCacheBase.cs index 05f7c36..a4ccd30 100644 --- a/AuroraControlsMaui/IconCacheBase.cs +++ b/AuroraControlsMaui/IconCacheBase.cs @@ -1,6 +1,5 @@ using System.Reflection; using System.Text; -using System.Xml; using Svg.Skia; namespace AuroraControls; @@ -19,6 +18,8 @@ public abstract class IconCacheBase : IIconCache, IDisposable private readonly float _platformScalingFactor; + private readonly SKColorSpace _colorspace = SKColorSpace.CreateSrgb(); + private bool _disposedValue; public IconCacheBase() @@ -27,12 +28,12 @@ public IconCacheBase() _platformScalingFactor = (float)PlatformInfo.ScalingFactor; } - public Task IconFromSvg(string svgName, double squareSize = 22d, string additionalCacheKey = "", Color colorOverride = default(Color)) + public Task IconFromSvg(string svgName, double squareSize = 22d, string additionalCacheKey = "", Color? colorOverride = null) { return IconFromSvg(svgName, new Size(squareSize, squareSize), additionalCacheKey, colorOverride); } - public async Task IconFromSvg(string svgName, Size size, string additionalCacheKey = "", Color colorOverride = default(Color)) + public async Task IconFromSvg(string svgName, Size size, string additionalCacheKey = "", Color? colorOverride = null) { return new Image() @@ -41,12 +42,12 @@ public IconCacheBase() }; } - public Task SourceFromSvg(string svgName, double squareSize = 22d, string additionalCacheKey = "", Color colorOverride = default(Color)) + public Task SourceFromSvg(string svgName, double squareSize = 22d, string additionalCacheKey = "", Color? colorOverride = null) { return SourceFromSvg(svgName, new Size(squareSize, squareSize), additionalCacheKey, colorOverride); } - public async Task SourceFromSvg(string svgName, Size size, string additionalCacheKey = "", Color colorOverride = default(Color)) + public async Task SourceFromSvg(string svgName, Size size, string additionalCacheKey = "", Color? colorOverride = null) { var key = CreateIconKey(svgName, size, additionalCacheKey, colorOverride); @@ -87,12 +88,12 @@ public IconCacheBase() } } - public Task SourceFromRawSvg(string svgName, string svgValue, double squareSize = 22d, string additionalCacheKey = "", Color colorOverride = default(Color)) + public Task SourceFromRawSvg(string svgName, string svgValue, double squareSize = 22d, string additionalCacheKey = "", Color? colorOverride = null) { return SourceFromRawSvg(svgName, svgValue, new Size(squareSize, squareSize), additionalCacheKey, colorOverride); } - public async Task SourceFromRawSvg(string svgName, string svgValue, Size size, string additionalCacheKey = "", Color colorOverride = default(Color)) + public async Task SourceFromRawSvg(string svgName, string svgValue, Size size, string additionalCacheKey = "", Color? colorOverride = null) { try { @@ -127,12 +128,12 @@ public IconCacheBase() } } - public Task FileImageSourceFromSvg(string svgName, double squareSize = 22d, string additionalCacheKey = "", Color colorOverride = default(Color)) + public Task FileImageSourceFromSvg(string svgName, double squareSize = 22d, string additionalCacheKey = "", Color? colorOverride = null) { return FileImageSourceFromSvg(svgName, new Size(squareSize, squareSize), additionalCacheKey, colorOverride); } - public async Task FileImageSourceFromSvg(string svgName, Size size, string additionalCacheKey = "", Color colorOverride = default(Color)) + public async Task FileImageSourceFromSvg(string svgName, Size size, string additionalCacheKey = "", Color? colorOverride = null) { try { @@ -159,6 +160,11 @@ public IconCacheBase() _resolvedIcons[key] = diskCachedImage; + if (DeviceInfo.Current.Platform == DevicePlatform.iOS) + { + return new FileImageSource { File = diskCachedImage }; + } + return new FileImageSource { File = diskCachedImage }; } catch (Exception ex) @@ -173,7 +179,7 @@ public IconCacheBase() } } - private string GetImagePathFromDiskCache(string key) + private string? GetImagePathFromDiskCache(string key) { if (!Directory.Exists(PlatformInfo.IconCacheDirectory)) { @@ -187,6 +193,12 @@ private string GetImagePathFromDiskCache(string key) return null; } + if (DeviceInfo.Current.Platform == DevicePlatform.iOS || + DeviceInfo.Current.Platform == DevicePlatform.MacCatalyst) + { + return $"{filePath.Split('@')[0]}.png"; + } + return filePath; } @@ -201,7 +213,7 @@ private async Task SaveIconToDiskCache(string key, Stream imageStream) } } - private async Task GenerateImageFromEmbedded(string key, string svgName, Size size, Color colorOverride) + private async Task GenerateImageFromEmbedded(string key, string svgName, Size size, Color? colorOverride = null) { try { @@ -235,7 +247,7 @@ private async Task GenerateImageFromEmbedded(string key, string svgName, Size si } } - private async Task GenerateImageFromRaw(string key, string svgValue, Size size, Color colorOverride) + private async Task GenerateImageFromRaw(string key, string svgValue, Size size, Color? colorOverride = null) { try { @@ -267,9 +279,11 @@ private async Task GenerateImageFromRaw(string key, string svgValue, Size size, } } - private async Task RenderSvgAsync(SKSvg skSvg, string key, Size size, Color colorOverride) + private async Task RenderSvgAsync(SKSvg skSvg, string key, Size size, Color? colorOverride) { - var scaledCanvas = _platformScalingFactor; + var platformScalingFactor = _platformScalingFactor; + + var scaledCanvas = platformScalingFactor; SKRect resize = skSvg.Picture.CullRect; @@ -277,45 +291,50 @@ private async Task RenderSvgAsync(SKSvg skSvg, string key, Size size, Color colo { var minSize = (float)Math.Min(size.Width, size.Height); - scaledCanvas = (minSize / Math.Max(skSvg.Picture.CullRect.Width, skSvg.Picture.CullRect.Height)) * _platformScalingFactor; + scaledCanvas = (float)Math.Round((minSize / Math.Max(skSvg.Picture.CullRect.Width, skSvg.Picture.CullRect.Height)) * platformScalingFactor, 0, MidpointRounding.ToEven); resize = new SKRect(0, 0, skSvg.Picture.CullRect.Width * scaledCanvas, skSvg.Picture.CullRect.Height * scaledCanvas); } else if (skSvg.Picture.CullRect != default) { - resize = new SKRect(0, 0, skSvg.Picture.CullRect.Width * _platformScalingFactor, skSvg.Picture.CullRect.Height * _platformScalingFactor); + resize = new SKRect(0, 0, skSvg.Picture.CullRect.Width * platformScalingFactor, skSvg.Picture.CullRect.Height * platformScalingFactor); } - if (colorOverride == default(Color)) + if (colorOverride == null) { using var memStream = new MemoryStream(); + skSvg.Picture.ToImage(memStream, SKColors.Empty, SKEncodedImageFormat.Png, 100, scaledCanvas, scaledCanvas, SKColorType.Rgba8888, SKAlphaType.Premul, SKColorSpace.CreateSrgb()); + + // var img = SKImage.FromPicture(skSvg.Picture, resize.Size.ToSizeI()); + // using var encoded = img.Encode(SKEncodedImageFormat.Png, 100); memStream.Seek(0L, SeekOrigin.Begin); + + // encoded.SaveTo(memStream); await SaveIconToDiskCache(key, memStream).ConfigureAwait(false); return; } - var imageInfo = new SKImageInfo((int)resize.Width, (int)resize.Height, SKColorType.Rgba8888, SKAlphaType.Premul); + var imageInfo = new SKImageInfo((int)Math.Ceiling(resize.Width), (int)Math.Ceiling(resize.Height), SKColorType.Rgba8888, SKAlphaType.Premul); - using (var bmp = new SKBitmap(imageInfo)) - using (var canvas = new SKCanvas(bmp)) - { - skSvg.Picture.Draw(SKColor.Empty, scaledCanvas, scaledCanvas, canvas); + using var bmp = new SKBitmap(imageInfo); + using var canvas = new SKCanvas(bmp); - if (colorOverride != default(Color)) - { - _paint.Color = colorOverride.ToSKColor(); - canvas.DrawPaint(_paint); - } + skSvg.Picture.Draw(SKColor.Empty, scaledCanvas, scaledCanvas, canvas); - canvas.Flush(); + if (colorOverride != default(Color)) + { + this._paint.Color = colorOverride.ToSKColor(); + canvas.DrawPaint(this._paint); + } - using (var image = SKImage.FromBitmap(bmp)) - using (var data = image.Encode(SKEncodedImageFormat.Png, 100)) - using (var stream = data.AsStream(false)) - { - await SaveIconToDiskCache(key, stream).ConfigureAwait(false); - } + canvas.Flush(); + + using (var image = SKImage.FromBitmap(bmp)) + using (var data = image.Encode(SKEncodedImageFormat.Png, 100)) + using (var stream = data.AsStream(false)) + { + await this.SaveIconToDiskCache(key, stream).ConfigureAwait(false); } } @@ -352,14 +371,11 @@ public Task ClearCache() public abstract Task StreamFromSource(ImageSource imageSource); - private string CreateIconKey(string svgName, Size size, string additionalCacheKey = "", Color colorOverride = default(Color)) + private string CreateIconKey(string svgName, Size size, string additionalCacheKey = "", Color colorOverride = null) { var key = $"{svgName}_{additionalCacheKey}_{size.Width}_{size.Height}_{colorOverride?.GetHashCode()}".Trim(_underscore); - if (_platformScalingFactor > 1.01f) - { - key = $"{key}@{_platformScalingFactor}x"; - } + key = $"{key}@{_platformScalingFactor}x"; key = string.Join(_underscore, key.Split(_invalidFilenameChars)); @@ -368,16 +384,16 @@ public Task ClearCache() protected virtual void Dispose(bool disposing) { - if (!_disposedValue) + if (_disposedValue || !disposing) { - if (disposing) - { - _iconLock?.Dispose(); - _paint.Dispose(); - } - - _disposedValue = true; + return; } + + this._iconLock?.Dispose(); + this._colorspace.Dispose(); + this._paint.Dispose(); + + this._disposedValue = true; } public void Dispose()