Skip to content

Commit

Permalink
Merge pull request #14 from TheEightBot/feature/icon-cache-updates
Browse files Browse the repository at this point in the history
Add SVG icon support and clean up color handling
  • Loading branch information
michaelstonis authored Jan 21, 2025
2 parents b7a0d88 + 5c80e05 commit a7f5bd5
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 55 deletions.
4 changes: 4 additions & 0 deletions AuroraControls.TestApp/MainPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ public MainPage(ILogger<TestRxViewModel> logger)
ViewModel = new TestRxViewModel();
MvvmToolkitViewModel = new TestMvvmToolkitViewModel();

this.ToolbarItems.Add(
new ToolbarItem()
.SetSvgIcon("more.svg"));

Content =
new ScrollView
{
Expand Down
11 changes: 11 additions & 0 deletions AuroraControls.TestApp/Resources/SVG/more.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 8 additions & 8 deletions AuroraControlsMaui/IIconCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public interface IIconCache
/// <param name="squareSize">The square size of the icon.</param>
/// <param name="additionalCacheKey">Allows for setting an addiitonal cache key.</param>
/// <param name="colorOverride">Allows for setting the color of the icon.</param>
Task<Image> IconFromSvg(string svgName, double squareSize = 22d, string additionalCacheKey = "", Color colorOverride = default(Color));
Task<Image> IconFromSvg(string svgName, double squareSize = 22d, string additionalCacheKey = "", Color? colorOverride = default(Color));

/// <summary>
/// Fetches an SVG icon by name.
Expand All @@ -25,7 +25,7 @@ public interface IIconCache
/// <param name="size">A Xamarin.Forms.Size representing the desired size of the icon.</param>
/// <param name="additionalCacheKey">Allows for setting an addiitonal cache key.</param>
/// <param name="colorOverride">Allows for setting the color of the icon.</param>
Task<Image> IconFromSvg(string svgName, Size size, string additionalCacheKey = "", Color colorOverride = default(Color));
Task<Image> IconFromSvg(string svgName, Size size, string additionalCacheKey = "", Color? colorOverride = default(Color));

/// <summary>
/// Fetches an SVG source by name.
Expand All @@ -35,7 +35,7 @@ public interface IIconCache
/// <param name="squareSize">The square size of the icon.</param>
/// <param name="additionalCacheKey">Allows for setting an addiitonal cache key.</param>
/// <param name="colorOverride">Allows for setting the color of the icon.</param>
Task<ImageSource> SourceFromSvg(string svgName, double squareSize = 22d, string additionalCacheKey = "", Color colorOverride = default(Color));
Task<ImageSource> SourceFromSvg(string svgName, double squareSize = 22d, string additionalCacheKey = "", Color? colorOverride = default(Color));

/// <summary>
/// Fetches an SVG icon by name.
Expand All @@ -45,7 +45,7 @@ public interface IIconCache
/// <param name="size">A Xamarin.Forms.Size representing the desired size of the icon.</param>
/// <param name="additionalCacheKey">Allows for setting an addiitonal cache key.</param>
/// <param name="colorOverride">Allows for setting the color of the icon.</param>
Task<ImageSource> SourceFromSvg(string svgName, Size size, string additionalCacheKey = "", Color colorOverride = default(Color));
Task<ImageSource> SourceFromSvg(string svgName, Size size, string additionalCacheKey = "", Color? colorOverride = default(Color));

/// <summary>
/// Fetches an SVG icon by name.
Expand All @@ -56,7 +56,7 @@ public interface IIconCache
/// <param name="squareSize">A double representing the desired size of the icon.</param>
/// <param name="additionalCacheKey">Allows for setting an addiitonal cache key.</param>
/// <param name="colorOverride">Allows for setting the color of the icon.</param>
Task<ImageSource> SourceFromRawSvg(string svgName, string svgValue, double squareSize = 22d, string additionalCacheKey = "", Color colorOverride = default(Color));
Task<ImageSource> SourceFromRawSvg(string svgName, string svgValue, double squareSize = 22d, string additionalCacheKey = "", Color? colorOverride = default(Color));

/// <summary>
/// Fetches an SVG icon by name.
Expand All @@ -67,7 +67,7 @@ public interface IIconCache
/// <param name="size">A Xamarin.Forms.Size representing the desired size of the icon.</param>
/// <param name="additionalCacheKey">Allows for setting an addiitonal cache key.</param>
/// <param name="colorOverride">Allows for setting the color of the icon.</param>
Task<ImageSource> SourceFromRawSvg(string svgName, string svgValue, Size size, string additionalCacheKey = "", Color colorOverride = default(Color));
Task<ImageSource> SourceFromRawSvg(string svgName, string svgValue, Size size, string additionalCacheKey = "", Color? colorOverride = default(Color));

/// <summary>
/// Fetches an SVG file image source by name.
Expand All @@ -77,7 +77,7 @@ public interface IIconCache
/// <param name="squareSize">The square size of the icon.</param>
/// <param name="additionalCacheKey">Allows for setting an addiitonal cache key.</param>
/// <param name="colorOverride">Allows for setting the color of the icon.</param>
Task<FileImageSource> FileImageSourceFromSvg(string svgName, double squareSize = 22d, string additionalCacheKey = "", Color colorOverride = default(Color));
Task<FileImageSource> FileImageSourceFromSvg(string svgName, double squareSize = 22d, string additionalCacheKey = "", Color? colorOverride = default(Color));

/// <summary>
/// Fetches an SVG icon by name.
Expand All @@ -87,7 +87,7 @@ public interface IIconCache
/// <param name="size">A Xamarin.Forms.Size representing the desired size of the icon.</param>
/// <param name="additionalCacheKey">Allows for setting an addiitonal cache key.</param>
/// <param name="colorOverride">Allows for setting the color of the icon.</param>
Task<FileImageSource> FileImageSourceFromSvg(string svgName, Size size, string additionalCacheKey = "", Color colorOverride = default(Color));
Task<FileImageSource> FileImageSourceFromSvg(string svgName, Size size, string additionalCacheKey = "", Color? colorOverride = default(Color));

/// <summary>
/// Loads the assembly.
Expand Down
110 changes: 63 additions & 47 deletions AuroraControlsMaui/IconCacheBase.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System.Reflection;
using System.Text;
using System.Xml;
using Svg.Skia;

namespace AuroraControls;
Expand All @@ -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()
Expand All @@ -27,12 +28,12 @@ public IconCacheBase()
_platformScalingFactor = (float)PlatformInfo.ScalingFactor;
}

public Task<Image> IconFromSvg(string svgName, double squareSize = 22d, string additionalCacheKey = "", Color colorOverride = default(Color))
public Task<Image> IconFromSvg(string svgName, double squareSize = 22d, string additionalCacheKey = "", Color? colorOverride = null)
{
return IconFromSvg(svgName, new Size(squareSize, squareSize), additionalCacheKey, colorOverride);
}

public async Task<Image> IconFromSvg(string svgName, Size size, string additionalCacheKey = "", Color colorOverride = default(Color))
public async Task<Image> IconFromSvg(string svgName, Size size, string additionalCacheKey = "", Color? colorOverride = null)
{
return
new Image()
Expand All @@ -41,12 +42,12 @@ public IconCacheBase()
};
}

public Task<ImageSource> SourceFromSvg(string svgName, double squareSize = 22d, string additionalCacheKey = "", Color colorOverride = default(Color))
public Task<ImageSource> SourceFromSvg(string svgName, double squareSize = 22d, string additionalCacheKey = "", Color? colorOverride = null)
{
return SourceFromSvg(svgName, new Size(squareSize, squareSize), additionalCacheKey, colorOverride);
}

public async Task<ImageSource> SourceFromSvg(string svgName, Size size, string additionalCacheKey = "", Color colorOverride = default(Color))
public async Task<ImageSource> SourceFromSvg(string svgName, Size size, string additionalCacheKey = "", Color? colorOverride = null)
{
var key = CreateIconKey(svgName, size, additionalCacheKey, colorOverride);

Expand Down Expand Up @@ -87,12 +88,12 @@ public IconCacheBase()
}
}

public Task<ImageSource> SourceFromRawSvg(string svgName, string svgValue, double squareSize = 22d, string additionalCacheKey = "", Color colorOverride = default(Color))
public Task<ImageSource> 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<ImageSource> SourceFromRawSvg(string svgName, string svgValue, Size size, string additionalCacheKey = "", Color colorOverride = default(Color))
public async Task<ImageSource> SourceFromRawSvg(string svgName, string svgValue, Size size, string additionalCacheKey = "", Color? colorOverride = null)
{
try
{
Expand Down Expand Up @@ -127,12 +128,12 @@ public IconCacheBase()
}
}

public Task<FileImageSource> FileImageSourceFromSvg(string svgName, double squareSize = 22d, string additionalCacheKey = "", Color colorOverride = default(Color))
public Task<FileImageSource> FileImageSourceFromSvg(string svgName, double squareSize = 22d, string additionalCacheKey = "", Color? colorOverride = null)
{
return FileImageSourceFromSvg(svgName, new Size(squareSize, squareSize), additionalCacheKey, colorOverride);
}

public async Task<FileImageSource> FileImageSourceFromSvg(string svgName, Size size, string additionalCacheKey = "", Color colorOverride = default(Color))
public async Task<FileImageSource> FileImageSourceFromSvg(string svgName, Size size, string additionalCacheKey = "", Color? colorOverride = null)
{
try
{
Expand All @@ -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)
Expand All @@ -173,7 +179,7 @@ public IconCacheBase()
}
}

private string GetImagePathFromDiskCache(string key)
private string? GetImagePathFromDiskCache(string key)
{
if (!Directory.Exists(PlatformInfo.IconCacheDirectory))
{
Expand All @@ -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;
}

Expand All @@ -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
{
Expand Down Expand Up @@ -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
{
Expand Down Expand Up @@ -267,55 +279,62 @@ 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;

if (size != default(Size) && skSvg.Picture.CullRect != default)
{
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);
}
}

Expand Down Expand Up @@ -352,14 +371,11 @@ public Task ClearCache()

public abstract Task<Stream> 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));

Expand All @@ -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()
Expand Down

0 comments on commit a7f5bd5

Please sign in to comment.