Use SkiaSharp for Avalonia in place of ImageSharp (#6269)

* Rebased

Transformation all at once

Use SkiaSharp instead of ImageSharp

* Apply suggestions from code review

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Change back unintentionally changed comment

---------

Co-authored-by: Ac_K <Acoustik666@gmail.com>
Co-authored-by: Emmanuel Hansen <emmausssss@gmail.com>
This commit is contained in:
sunshineinabox 2024-07-14 01:16:14 -07:00 committed by GitHub
parent 07435ad844
commit 595e514f18
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 104 additions and 71 deletions

View file

@ -40,20 +40,17 @@ using Ryujinx.UI.Common;
using Ryujinx.UI.Common.Configuration; using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.Helper; using Ryujinx.UI.Common.Helper;
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
using SixLabors.ImageSharp; using SkiaSharp;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SPB.Graphics.Vulkan; using SPB.Graphics.Vulkan;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop; using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop;
using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing; using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing;
using Image = SixLabors.ImageSharp.Image;
using InputManager = Ryujinx.Input.HLE.InputManager; using InputManager = Ryujinx.Input.HLE.InputManager;
using IRenderer = Ryujinx.Graphics.GAL.IRenderer; using IRenderer = Ryujinx.Graphics.GAL.IRenderer;
using Key = Ryujinx.Input.Key; using Key = Ryujinx.Input.Key;
@ -366,25 +363,33 @@ namespace Ryujinx.Ava
return; return;
} }
Image image = e.IsBgra ? Image.LoadPixelData<Bgra32>(e.Data, e.Width, e.Height) var colorType = e.IsBgra ? SKColorType.Bgra8888 : SKColorType.Rgba8888;
: Image.LoadPixelData<Rgba32>(e.Data, e.Width, e.Height); using var bitmap = new SKBitmap(new SKImageInfo(e.Width, e.Height, colorType, SKAlphaType.Premul));
if (e.FlipX) Marshal.Copy(e.Data, 0, bitmap.GetPixels(), e.Data.Length);
SKBitmap bitmapToSave = null;
if (e.FlipX || e.FlipY)
{ {
image.Mutate(x => x.Flip(FlipMode.Horizontal)); bitmapToSave = new SKBitmap(bitmap.Width, bitmap.Height);
using var canvas = new SKCanvas(bitmapToSave);
canvas.Clear(SKColors.Transparent);
float scaleX = e.FlipX ? -1 : 1;
float scaleY = e.FlipY ? -1 : 1;
var matrix = SKMatrix.CreateScale(scaleX, scaleY, bitmap.Width / 2f, bitmap.Height / 2f);
canvas.SetMatrix(matrix);
canvas.DrawBitmap(bitmap, new SKPoint(e.FlipX ? -bitmap.Width : 0, e.FlipY ? -bitmap.Height : 0));
} }
if (e.FlipY) SaveBitmapAsPng(bitmapToSave ?? bitmap, path);
{ bitmapToSave?.Dispose();
image.Mutate(x => x.Flip(FlipMode.Vertical));
}
image.SaveAsPng(path, new PngEncoder
{
ColorType = PngColorType.Rgb,
});
image.Dispose();
Logger.Notice.Print(LogClass.Application, $"Screenshot saved to {path}", "Screenshot"); Logger.Notice.Print(LogClass.Application, $"Screenshot saved to {path}", "Screenshot");
} }
@ -396,6 +401,14 @@ namespace Ryujinx.Ava
} }
} }
private void SaveBitmapAsPng(SKBitmap bitmap, string path)
{
using var data = bitmap.Encode(SKEncodedImageFormat.Png, 100);
using var stream = File.OpenWrite(path);
data.SaveTo(stream);
}
public void Start() public void Start()
{ {
if (OperatingSystem.IsWindows()) if (OperatingSystem.IsWindows())

View file

@ -54,7 +54,6 @@
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" /> <PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" />
<PackageReference Include="SPB" /> <PackageReference Include="SPB" />
<PackageReference Include="SharpZipLib" /> <PackageReference Include="SharpZipLib" />
<PackageReference Include="SixLabors.ImageSharp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -32,7 +32,7 @@ using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common; using Ryujinx.UI.Common;
using Ryujinx.UI.Common.Configuration; using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.Helper; using Ryujinx.UI.Common.Helper;
using SixLabors.ImageSharp.PixelFormats; using SkiaSharp;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
@ -40,7 +40,6 @@ using System.Globalization;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Image = SixLabors.ImageSharp.Image;
using Key = Ryujinx.Input.Key; using Key = Ryujinx.Input.Key;
using MissingKeyException = LibHac.Common.Keys.MissingKeyException; using MissingKeyException = LibHac.Common.Keys.MissingKeyException;
using ShaderCacheLoadingState = Ryujinx.Graphics.Gpu.Shader.ShaderCacheState; using ShaderCacheLoadingState = Ryujinx.Graphics.Gpu.Shader.ShaderCacheState;
@ -1164,17 +1163,17 @@ namespace Ryujinx.Ava.UI.ViewModels
private void PrepareLoadScreen() private void PrepareLoadScreen()
{ {
using MemoryStream stream = new(SelectedIcon); using MemoryStream stream = new(SelectedIcon);
using var gameIconBmp = Image.Load<Bgra32>(stream); using var gameIconBmp = SKBitmap.Decode(stream);
var dominantColor = IconColorPicker.GetFilteredColor(gameIconBmp).ToPixel<Bgra32>(); var dominantColor = IconColorPicker.GetFilteredColor(gameIconBmp);
const float ColorMultiple = 0.5f; const float ColorMultiple = 0.5f;
Color progressFgColor = Color.FromRgb(dominantColor.R, dominantColor.G, dominantColor.B); Color progressFgColor = Color.FromRgb(dominantColor.Red, dominantColor.Green, dominantColor.Blue);
Color progressBgColor = Color.FromRgb( Color progressBgColor = Color.FromRgb(
(byte)(dominantColor.R * ColorMultiple), (byte)(dominantColor.Red * ColorMultiple),
(byte)(dominantColor.G * ColorMultiple), (byte)(dominantColor.Green * ColorMultiple),
(byte)(dominantColor.B * ColorMultiple)); (byte)(dominantColor.Blue * ColorMultiple));
ProgressBarForegroundColor = new SolidColorBrush(progressFgColor); ProgressBarForegroundColor = new SolidColorBrush(progressFgColor);
ProgressBarBackgroundColor = new SolidColorBrush(progressBgColor); ProgressBarBackgroundColor = new SolidColorBrush(progressBgColor);

View file

@ -9,14 +9,14 @@ using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils; using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.UI.Models; using Ryujinx.Ava.UI.Models;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using SixLabors.ImageSharp; using SkiaSharp;
using SixLabors.ImageSharp.PixelFormats;
using System; using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.IO; using System.IO;
using Color = Avalonia.Media.Color; using Color = Avalonia.Media.Color;
using Image = SkiaSharp.SKImage;
namespace Ryujinx.Ava.UI.ViewModels namespace Ryujinx.Ava.UI.ViewModels
{ {
@ -130,9 +130,12 @@ namespace Ryujinx.Ava.UI.ViewModels
stream.Position = 0; stream.Position = 0;
Image avatarImage = Image.LoadPixelData<Rgba32>(DecompressYaz0(stream), 256, 256); Image avatarImage = Image.FromPixelCopy(new SKImageInfo(256, 256, SKColorType.Rgba8888, SKAlphaType.Premul), DecompressYaz0(stream));
avatarImage.SaveAsPng(streamPng); using (SKData data = avatarImage.Encode(SKEncodedImageFormat.Png, 100))
{
data.SaveTo(streamPng);
}
_avatarStore.Add(item.FullPath, streamPng.ToArray()); _avatarStore.Add(item.FullPath, streamPng.ToArray());
} }

View file

@ -6,12 +6,8 @@ using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Models; using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using SixLabors.ImageSharp; using SkiaSharp;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using System.IO; using System.IO;
using Image = SixLabors.ImageSharp.Image;
namespace Ryujinx.Ava.UI.Views.User namespace Ryujinx.Ava.UI.Views.User
{ {
@ -70,15 +66,25 @@ namespace Ryujinx.Ava.UI.Views.User
{ {
if (ViewModel.SelectedImage != null) if (ViewModel.SelectedImage != null)
{ {
MemoryStream streamJpg = new(); using var streamJpg = new MemoryStream();
Image avatarImage = Image.Load(ViewModel.SelectedImage, new PngDecoder()); using var bitmap = SKBitmap.Decode(ViewModel.SelectedImage);
using var newBitmap = new SKBitmap(bitmap.Width, bitmap.Height);
avatarImage.Mutate(x => x.BackgroundColor(new Rgba32( using (var canvas = new SKCanvas(newBitmap))
ViewModel.BackgroundColor.R, {
ViewModel.BackgroundColor.G, canvas.Clear(new SKColor(
ViewModel.BackgroundColor.B, ViewModel.BackgroundColor.R,
ViewModel.BackgroundColor.A))); ViewModel.BackgroundColor.G,
avatarImage.SaveAsJpeg(streamJpg); ViewModel.BackgroundColor.B,
ViewModel.BackgroundColor.A));
canvas.DrawBitmap(bitmap, 0, 0);
}
using (var image = SKImage.FromBitmap(newBitmap))
using (var dataJpeg = image.Encode(SKEncodedImageFormat.Jpeg, 100))
{
dataJpeg.SaveTo(streamJpg);
}
_profile.Image = streamJpg.ToArray(); _profile.Image = streamJpg.ToArray();

View file

@ -9,11 +9,9 @@ using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Models; using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using SixLabors.ImageSharp; using SkiaSharp;
using SixLabors.ImageSharp.Processing;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using Image = SixLabors.ImageSharp.Image;
namespace Ryujinx.Ava.UI.Views.User namespace Ryujinx.Ava.UI.Views.User
{ {
@ -102,13 +100,19 @@ namespace Ryujinx.Ava.UI.Views.User
private static byte[] ProcessProfileImage(byte[] buffer) private static byte[] ProcessProfileImage(byte[] buffer)
{ {
using Image image = Image.Load(buffer); using var bitmap = SKBitmap.Decode(buffer);
image.Mutate(x => x.Resize(256, 256)); var resizedBitmap = bitmap.Resize(new SKImageInfo(256, 256), SKFilterQuality.High);
using MemoryStream streamJpg = new(); using var streamJpg = new MemoryStream();
image.SaveAsJpeg(streamJpg); if (resizedBitmap != null)
{
using var image = SKImage.FromBitmap(resizedBitmap);
using var dataJpeg = image.Encode(SKEncodedImageFormat.Jpeg, 100);
dataJpeg.SaveTo(streamJpg);
}
return streamJpg.ToArray(); return streamJpg.ToArray();
} }

View file

@ -1,5 +1,4 @@
using SixLabors.ImageSharp; using SkiaSharp;
using SixLabors.ImageSharp.PixelFormats;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -36,35 +35,34 @@ namespace Ryujinx.Ava.UI.Windows
} }
} }
public static Color GetFilteredColor(Image<Bgra32> image) public static SKColor GetFilteredColor(SKBitmap image)
{ {
var color = GetColor(image).ToPixel<Bgra32>(); var color = GetColor(image);
// We don't want colors that are too dark. // We don't want colors that are too dark.
// If the color is too dark, make it brighter by reducing the range // If the color is too dark, make it brighter by reducing the range
// and adding a constant color. // and adding a constant color.
int luminosity = GetColorApproximateLuminosity(color.R, color.G, color.B); int luminosity = GetColorApproximateLuminosity(color.Red, color.Green, color.Blue);
if (luminosity < CutOffLuminosity) if (luminosity < CutOffLuminosity)
{ {
color = Color.FromRgb( color = new SKColor(
(byte)Math.Min(CutOffLuminosity + color.R, byte.MaxValue), (byte)Math.Min(CutOffLuminosity + color.Red, byte.MaxValue),
(byte)Math.Min(CutOffLuminosity + color.G, byte.MaxValue), (byte)Math.Min(CutOffLuminosity + color.Green, byte.MaxValue),
(byte)Math.Min(CutOffLuminosity + color.B, byte.MaxValue)); (byte)Math.Min(CutOffLuminosity + color.Blue, byte.MaxValue));
} }
return color; return color;
} }
public static Color GetColor(Image<Bgra32> image) public static SKColor GetColor(SKBitmap image)
{ {
var colors = new PaletteColor[TotalColors]; var colors = new PaletteColor[TotalColors];
var dominantColorBin = new Dictionary<int, int>(); var dominantColorBin = new Dictionary<int, int>();
var buffer = GetBuffer(image); var buffer = GetBuffer(image);
int w = image.Width; int w = image.Width;
int w8 = w << 8; int w8 = w << 8;
int h8 = image.Height << 8; int h8 = image.Height << 8;
@ -84,9 +82,10 @@ namespace Ryujinx.Ava.UI.Windows
{ {
int offset = x + yOffset; int offset = x + yOffset;
byte cb = buffer[offset].B; SKColor pixel = buffer[offset];
byte cg = buffer[offset].G; byte cr = pixel.Red;
byte cr = buffer[offset].R; byte cg = pixel.Green;
byte cb = pixel.Blue;
var qck = GetQuantizedColorKey(cr, cg, cb); var qck = GetQuantizedColorKey(cr, cg, cb);
@ -122,12 +121,22 @@ namespace Ryujinx.Ava.UI.Windows
} }
} }
return Color.FromRgb(bestCandidate.R, bestCandidate.G, bestCandidate.B); return new SKColor(bestCandidate.R, bestCandidate.G, bestCandidate.B);
} }
public static Bgra32[] GetBuffer(Image<Bgra32> image) public static SKColor[] GetBuffer(SKBitmap image)
{ {
return image.DangerousTryGetSinglePixelMemory(out var data) ? data.ToArray() : Array.Empty<Bgra32>(); var pixels = new SKColor[image.Width * image.Height];
for (int y = 0; y < image.Height; y++)
{
for (int x = 0; x < image.Width; x++)
{
pixels[x + y * image.Width] = image.GetPixel(x, y);
}
}
return pixels;
} }
private static int GetColorScore(Dictionary<int, int> dominantColorBin, int maxHitCount, PaletteColor color) private static int GetColorScore(Dictionary<int, int> dominantColorBin, int maxHitCount, PaletteColor color)