Skip to content

Commit

Permalink
Merge pull request #759 from punker76/feature/GH-756
Browse files Browse the repository at this point in the history
(GH-756) Improve Svg icon rendering by using Svg.Skia (SkiaSharp)
  • Loading branch information
gep13 authored Apr 11, 2020
2 parents 140f9a5 + c490398 commit 1d87ac2
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,18 @@
<Reference Include="ControlzEx, Version=4.0.0.0, Culture=neutral, PublicKeyToken=69f1c32f803d307e, processorArchitecture=MSIL">
<HintPath>..\packages\ControlzEx.4.1.1\lib\net45\ControlzEx.dll</HintPath>
</Reference>
<Reference Include="Fizzler, Version=1.2.0.0, Culture=neutral, PublicKeyToken=4ebff4844e382110, processorArchitecture=MSIL">
<HintPath>..\packages\Fizzler.1.2.0\lib\netstandard1.0\Fizzler.dll</HintPath>
</Reference>
<Reference Include="HarfBuzzSharp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
<HintPath>..\packages\HarfBuzzSharp.2.6.1.1\lib\net45\HarfBuzzSharp.dll</HintPath>
</Reference>
<Reference Include="LiteDB, Version=3.1.4.0, Culture=neutral, PublicKeyToken=4ee40123013c9f27, processorArchitecture=MSIL">
<HintPath>..\packages\LiteDB.3.1.4\lib\net35\LiteDB.dll</HintPath>
</Reference>
<Reference Include="log4net, Version=1.2.13.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL">
<HintPath>..\packages\log4net.2.0.3\lib\net40-full\log4net.dll</HintPath>
</Reference>
<Reference Include="Magick.NET-Q16-AnyCPU, Version=7.5.0.0, Culture=neutral, PublicKeyToken=2004825badfa91ec, processorArchitecture=MSIL">
<HintPath>..\packages\Magick.NET-Q16-AnyCPU.7.5.0\lib\net40\Magick.NET-Q16-AnyCPU.dll</HintPath>
</Reference>
<Reference Include="MahApps.Metro, Version=2.0.0.0, Culture=neutral, PublicKeyToken=51482d6f650b2b3f, processorArchitecture=MSIL">
<HintPath>..\packages\MahApps.Metro.2.0.0-alpha0531\lib\net45\MahApps.Metro.dll</HintPath>
</Reference>
Expand Down Expand Up @@ -171,14 +174,32 @@
<Reference Include="Serilog.Sinks.RollingFile, Version=2.0.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL">
<HintPath>..\packages\Serilog.Sinks.RollingFile.3.3.0\lib\net45\Serilog.Sinks.RollingFile.dll</HintPath>
</Reference>
<Reference Include="SkiaSharp, Version=1.68.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
<HintPath>..\packages\SkiaSharp.1.68.1.1\lib\net45\SkiaSharp.dll</HintPath>
</Reference>
<Reference Include="SkiaSharp.HarfBuzz, Version=1.68.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
<HintPath>..\packages\SkiaSharp.HarfBuzz.1.68.1.1\lib\net45\SkiaSharp.HarfBuzz.dll</HintPath>
</Reference>
<Reference Include="Splat, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Splat.2.0.0\lib\Net45\Splat.dll</HintPath>
</Reference>
<Reference Include="Svg.Custom, Version=0.0.0.0, Culture=neutral, PublicKeyToken=dafe96fe6c845a74, processorArchitecture=MSIL">
<HintPath>..\packages\Svg.Custom.0.1.7\lib\net452\Svg.Custom.dll</HintPath>
</Reference>
<Reference Include="Svg.Skia, Version=0.1.7.0, Culture=neutral, PublicKeyToken=dafe96fe6c845a74, processorArchitecture=MSIL">
<HintPath>..\packages\Svg.Skia.0.1.7\lib\net452\Svg.Skia.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Buffers.4.5.1\lib\netstandard1.1\System.Buffers.dll</HintPath>
</Reference>
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Memory, Version=4.0.1.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Memory.4.5.4\lib\netstandard1.1\System.Memory.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http" />
<Reference Include="System.Reactive.Core, Version=3.0.1000.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263, processorArchitecture=MSIL">
<HintPath>..\packages\System.Reactive.Core.3.1.1\lib\net45\System.Reactive.Core.dll</HintPath>
Expand All @@ -197,6 +218,12 @@
</Reference>
<Reference Include="System.Runtime.Caching" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.6.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.4.7.1\lib\netstandard1.0\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.InteropServices.RuntimeInformation, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll</HintPath>
</Reference>
<Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.ValueTuple.4.5.0\lib\netstandard1.0\System.ValueTuple.dll</HintPath>
</Reference>
Expand Down Expand Up @@ -401,4 +428,13 @@
</Page>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\packages\HarfBuzzSharp.2.6.1.1\build\net45\HarfBuzzSharp.targets" Condition="Exists('..\packages\HarfBuzzSharp.2.6.1.1\build\net45\HarfBuzzSharp.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\HarfBuzzSharp.2.6.1.1\build\net45\HarfBuzzSharp.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\HarfBuzzSharp.2.6.1.1\build\net45\HarfBuzzSharp.targets'))" />
<Error Condition="!Exists('..\packages\SkiaSharp.1.68.1.1\build\net45\SkiaSharp.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\SkiaSharp.1.68.1.1\build\net45\SkiaSharp.targets'))" />
</Target>
<Import Project="..\packages\SkiaSharp.1.68.1.1\build\net45\SkiaSharp.targets" Condition="Exists('..\packages\SkiaSharp.1.68.1.1\build\net45\SkiaSharp.targets')" />
</Project>
151 changes: 92 additions & 59 deletions Source/ChocolateyGui.Common.Windows/Controls/InternetImage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,20 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Caliburn.Micro;
using ChocolateyGui.Common.Windows.Utilities.Extensions;
using ImageMagick;
using LiteDB;
using MahApps.Metro.IconPacks;
using Microsoft.VisualStudio.Threading;
using Serilog;
using SkiaSharp;
using Splat;
using Svg.Skia;
using ILogger = Serilog.ILogger;

namespace ChocolateyGui.Common.Windows.Controls
Expand Down Expand Up @@ -87,32 +87,101 @@ private static void UploadFileAndSetMetadata(DateTime absoluteExpiration, Memory
imageStream.Position = 0;
}

private static IMagickImage ExtractImage(MagickImageCollection imageCollection, Size desiredSize)
private static bool IsSvg(string url)
{
var imagesOrderedBySize = imageCollection
.OrderBy(f => f.Width)
.ThenBy(f => f.Height)
.ToList();

// if there is no matching image, get the largest image
return imagesOrderedBySize
.FirstOrDefault(f => f.Width >= desiredSize.Width
&& f.Height >= desiredSize.Height)
?? imagesOrderedBySize.Last();
var extension = Path.GetExtension(url)?.ToLower();
return extension == ".svg" || extension == ".svgz";
}

private static async Task ExtractImageFromStream(Size desiredSize, MagickReadSettings readSettings, Stream inputStream, MemoryStream imageStream)
private static void ExtractImageFromStream(string url, Size desiredSize, Stream inputStream, Stream imageStream)
{
using (var images = new MagickImageCollection(inputStream, readSettings))
if (IsSvg(url))
{
var image = ExtractImage(images, desiredSize);
using (var svg = new SKSvg())
{
try
{
svg.Load(inputStream);
}
catch (Exception exception)
{
Logger.Warning(exception, $"Something is wrong with: \"{url}\".");
}

image.Resize((int)desiredSize.Width, 0);
var skPicture = svg.Picture;
var imageInfo = new SKImageInfo((int)desiredSize.Width, (int)desiredSize.Height);
using (var surface = SKSurface.Create(imageInfo))
{
using (var canvas = surface.Canvas)
{
// calculate the scaling need to fit to desired size
var scaleX = desiredSize.Width / skPicture.CullRect.Width;
var scaleY = desiredSize.Height / skPicture.CullRect.Height;
var matrix = SKMatrix.MakeScale((float)scaleX, (float)scaleY);

// draw the svg
canvas.Clear(SKColors.Transparent);
canvas.DrawPicture(skPicture, ref matrix);
canvas.Flush();

using (var data = surface.Snapshot())
{
using (var pngImage = data.Encode(SKEncodedImageFormat.Png, 100))
{
pngImage.SaveTo(imageStream);
}
}
}
}
}
}
else
{
var bitmap = SKBitmap.Decode(inputStream);
if (bitmap != null)
{
var resizeInfo = GetResizeSkImageInfo(desiredSize, bitmap);
using (var resizedBitmap = bitmap.Resize(resizeInfo, SKFilterQuality.High))
{
bitmap.Dispose();

using (var image = SKImage.FromBitmap(resizedBitmap))
{
using (var data = image.Encode(SKEncodedImageFormat.Png, 100))
{
data.SaveTo(imageStream);
}
}
}
}
}
}

image.Write(imageStream, MagickFormat.Png);
private static SKImageInfo GetResizeSkImageInfo(Size desiredSize, SKBitmap bitmap)
{
var resizeInfo = new SKImageInfo((int)desiredSize.Width, (int)desiredSize.Height);

// Test whether there is more room in width or height
if (Math.Abs(bitmap.Width - desiredSize.Width) > Math.Abs(bitmap.Height - desiredSize.Height))
{
// More room in width, so leave image width set to canvas width
// and increase/decrease height by same ratio
var widthRatio = (double)desiredSize.Width / (double)bitmap.Width;
var newHeight = (int)Math.Floor(bitmap.Height * widthRatio);

await imageStream.FlushAsync();
resizeInfo.Height = newHeight;
}
else
{
// More room in height, so leave image height set to canvas height
// and increase/decrease width by same ratio
var heightRatio = (double)desiredSize.Height / (double)bitmap.Height;
var newWidth = (int)Math.Floor(bitmap.Width * heightRatio);

resizeInfo.Width = newWidth;
}

return resizeInfo;
}

private async Task<IBitmap> LoadImage(string url, Size desiredSize, DateTime absoluteExpiration)
Expand Down Expand Up @@ -143,8 +212,6 @@ private async Task<Stream> DownloadUrl(string url, Size desiredSize, DateTime ab
}
}

var readSettings = GetMagickReadSettings(url);

// If we couldn't find the image or it expired
using (var client = new HttpClient())
{
Expand All @@ -156,7 +223,7 @@ private async Task<Stream> DownloadUrl(string url, Size desiredSize, DateTime ab
await response.Content.CopyToAsync(memoryStream);
memoryStream.Position = 0;

await ExtractImageFromStream(desiredSize, readSettings, memoryStream, imageStream);
ExtractImageFromStream(url, desiredSize, memoryStream, imageStream);
}
}

Expand Down Expand Up @@ -192,61 +259,27 @@ private async Task SetImage(string url)
{
source = ErrorIcon.Value;
}
catch (ArgumentException)
catch (ArgumentException exception)
{
Logger.Warning("Got an invalid img url: \"{IconUrl}\".", url);
Logger.Warning(exception, $"Got an invalid img url: \"{url}\".");
source = ErrorIcon.Value;
}
catch (Exception exception)
{
Logger.Warning(exception, "Something went wrong with: \"{IconUrl}\".", url);
Logger.Warning(exception, $"Something went wrong with: \"{url}\".");
source = ErrorIcon.Value;
}

PART_Image.Source = source;
PART_Loading.IsActive = false;
}

private MagickReadSettings GetMagickReadSettings(string url)
{
var extension = GetExtension(url);

MagickFormat format;
if (Enum.TryParse<MagickFormat>(extension, true, out format) == false)
{
////throw new Exception($"Image format with extension '{extension}' from '{url}' is currently not supported.");

return new MagickReadSettings();
}

var readSettings = new MagickReadSettings { Format = format };
return readSettings;
}

private Size GetCurrentSize()
{
var scale = NativeMethods.GetScaleFactor();
var x = (int)Math.Round(ActualWidth * scale);
var y = (int)Math.Round(ActualHeight * scale);
return new Size(x, y);
}

private string GetExtension(string url)
{
Uri uri;
if (!Uri.TryCreate(url, UriKind.Absolute, out uri))
{
throw new ArgumentException(nameof(url));
}

var imagePart = uri.Segments.Last();
var fileTypeSeperator = imagePart.LastIndexOf(".", StringComparison.InvariantCulture);
if (fileTypeSeperator <= 0)
{
return string.Empty;
}

return imagePart.Substring(fileTypeSeperator + 1);
}
}
}
10 changes: 10 additions & 0 deletions Source/ChocolateyGui.Common.Windows/app.config
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.6.0" newVersion="4.0.6.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
14 changes: 13 additions & 1 deletion Source/ChocolateyGui.Common.Windows/packages.config
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,39 @@
<package id="Caliburn.Micro.Core" version="3.2.0" targetFramework="net40" requireReinstallation="true" />
<package id="chocolatey.lib" version="0.10.15" targetFramework="net452" />
<package id="ControlzEx" version="4.1.1" targetFramework="net452" />
<package id="Fizzler" version="1.2.0" targetFramework="net452" />
<package id="HarfBuzzSharp" version="2.6.1.1" targetFramework="net452" />
<package id="LiteDB" version="3.1.4" targetFramework="net452" />
<package id="log4net" version="2.0.3" targetFramework="net452" />
<package id="Magick.NET-Q16-AnyCPU" version="7.5.0" targetFramework="net452" />
<package id="MahApps.Metro" version="2.0.0-alpha0531" targetFramework="net452" />
<package id="MahApps.Metro.IconPacks" version="3.0.0-alpha0255" targetFramework="net452" />
<package id="Markdig.Signed" version="0.17.1" targetFramework="net452" />
<package id="Markdig.Wpf.Signed" version="0.3.0" targetFramework="net452" />
<package id="Microsoft.NETCore.Platforms" version="3.1.0" targetFramework="net452" />
<package id="Microsoft.VisualStudio.Threading" version="15.4.4" targetFramework="net452" />
<package id="Microsoft.VisualStudio.Validation" version="15.3.15" targetFramework="net452" />
<package id="Microsoft.Xaml.Behaviors.Wpf" version="1.0.30" targetFramework="net452" />
<package id="NETStandard.Library" version="2.0.3" targetFramework="net452" />
<package id="Serilog" version="2.5.0" targetFramework="net452" />
<package id="Serilog.Sinks.Async" version="1.1.0" targetFramework="net452" />
<package id="Serilog.Sinks.File" version="3.2.0" targetFramework="net452" />
<package id="Serilog.Sinks.RollingFile" version="3.3.0" targetFramework="net452" />
<package id="SkiaSharp" version="1.68.1.1" targetFramework="net452" />
<package id="SkiaSharp.HarfBuzz" version="1.68.1.1" targetFramework="net452" />
<package id="Splat" version="2.0.0" targetFramework="net452" />
<package id="StyleCop.Analyzers" version="1.0.2" targetFramework="net40" developmentDependency="true" />
<package id="Svg.Custom" version="0.1.7" targetFramework="net452" />
<package id="Svg.Skia" version="0.1.7" targetFramework="net452" />
<package id="System.Buffers" version="4.5.1" targetFramework="net452" />
<package id="System.Diagnostics.Contracts" version="4.3.0" targetFramework="net452" />
<package id="System.Memory" version="4.5.4" targetFramework="net452" />
<package id="System.Reactive" version="3.1.1" targetFramework="net452" />
<package id="System.Reactive.Core" version="3.1.1" targetFramework="net452" />
<package id="System.Reactive.Interfaces" version="3.1.1" targetFramework="net452" />
<package id="System.Reactive.Linq" version="3.1.1" targetFramework="net452" />
<package id="System.Reactive.PlatformServices" version="3.1.1" targetFramework="net452" />
<package id="System.Reactive.Windows.Threading" version="3.1.1" targetFramework="net452" />
<package id="System.Runtime.CompilerServices.Unsafe" version="4.7.1" targetFramework="net452" />
<package id="System.Runtime.InteropServices.RuntimeInformation" version="4.3.0" targetFramework="net452" />
<package id="System.ValueTuple" version="4.5.0" targetFramework="net452" />
</packages>
Loading

0 comments on commit 1d87ac2

Please sign in to comment.