aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEmmanuel Hansen <emmausssss@gmail.com>2024-08-31 14:32:53 +0000
committerGitHub <noreply@github.com>2024-08-31 11:32:53 -0300
commite0acde04bb032fd056904b909b3fd00c1a6fb996 (patch)
treec3f146228712153af6f277e0e874d83a56b31d06
parent3c61d560c39d6edf897183fe33b8047c25d2d895 (diff)
Replace ImageSharp with SkiaSharp everywhere (#7030)1.1.1381
* replace ImageSharp with SkiaSharp for inline keyboard applet rendering * fix avalonia inline keyboard input * remove image sharp from gtk3 project * add skiasharp linux assets * fix whitespace * fix format * fix ico image offset when saving shortcut to windows
-rw-r--r--Directory.Packages.props6
-rw-r--r--src/Ryujinx.Gtk3/Program.cs7
-rw-r--r--src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj1
-rw-r--r--src/Ryujinx.Gtk3/UI/RendererWidgetBase.cs39
-rw-r--r--src/Ryujinx.Gtk3/UI/Windows/AvatarWindow.cs29
-rw-r--r--src/Ryujinx.Gtk3/UI/Windows/UserProfilesManagerWindow.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRenderer.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs390
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Caps/CaptureManager.cs10
-rw-r--r--src/Ryujinx.HLE/Ryujinx.HLE.csproj5
-rw-r--r--src/Ryujinx.UI.Common/Helper/ShortcutHelper.cs28
-rw-r--r--src/Ryujinx/UI/Applet/AvaloniaDynamicTextInputHandler.cs9
-rw-r--r--src/Ryujinx/UI/Helpers/OffscreenTextBox.cs3
-rw-r--r--src/Ryujinx/UI/Windows/MainWindow.axaml4
14 files changed, 295 insertions, 255 deletions
diff --git a/Directory.Packages.props b/Directory.Packages.props
index c31099072..8a9fdc3be 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -42,11 +42,11 @@
42 <PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" /> 42 <PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" />
43 <PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" /> 43 <PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" />
44 <PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" /> 44 <PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
45 <PackageVersion Include="SixLabors.ImageSharp" Version="2.1.9" /> 45 <PackageVersion Include="SkiaSharp" Version="2.88.7" />
46 <PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0" /> 46 <PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.7" />
47 <PackageVersion Include="SPB" Version="0.0.4-build32" /> 47 <PackageVersion Include="SPB" Version="0.0.4-build32" />
48 <PackageVersion Include="System.IO.Hashing" Version="8.0.0" /> 48 <PackageVersion Include="System.IO.Hashing" Version="8.0.0" />
49 <PackageVersion Include="System.Management" Version="8.0.0" /> 49 <PackageVersion Include="System.Management" Version="8.0.0" />
50 <PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" /> 50 <PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
51 </ItemGroup> 51 </ItemGroup>
52</Project> \ No newline at end of file 52</Project>
diff --git a/src/Ryujinx.Gtk3/Program.cs b/src/Ryujinx.Gtk3/Program.cs
index 8bb651640..745335ac9 100644
--- a/src/Ryujinx.Gtk3/Program.cs
+++ b/src/Ryujinx.Gtk3/Program.cs
@@ -13,7 +13,6 @@ using Ryujinx.UI.Common.Configuration;
13using Ryujinx.UI.Common.Helper; 13using Ryujinx.UI.Common.Helper;
14using Ryujinx.UI.Common.SystemInfo; 14using Ryujinx.UI.Common.SystemInfo;
15using Ryujinx.UI.Widgets; 15using Ryujinx.UI.Widgets;
16using SixLabors.ImageSharp.Formats.Jpeg;
17using System; 16using System;
18using System.Collections.Generic; 17using System.Collections.Generic;
19using System.Diagnostics; 18using System.Diagnostics;
@@ -162,12 +161,6 @@ namespace Ryujinx
162 }); 161 });
163 }; 162 };
164 163
165 // Sets ImageSharp Jpeg Encoder Quality.
166 SixLabors.ImageSharp.Configuration.Default.ImageFormatsManager.SetEncoder(JpegFormat.Instance, new JpegEncoder()
167 {
168 Quality = 100,
169 });
170
171 string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName); 164 string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName);
172 string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName); 165 string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName);
173 166
diff --git a/src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj b/src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj
index b4453f9d7..722d6080b 100644
--- a/src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj
+++ b/src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj
@@ -30,7 +30,6 @@
30 <PackageReference Include="OpenTK.Graphics" /> 30 <PackageReference Include="OpenTK.Graphics" />
31 <PackageReference Include="SPB" /> 31 <PackageReference Include="SPB" />
32 <PackageReference Include="SharpZipLib" /> 32 <PackageReference Include="SharpZipLib" />
33 <PackageReference Include="SixLabors.ImageSharp" />
34 </ItemGroup> 33 </ItemGroup>
35 34
36 <ItemGroup> 35 <ItemGroup>
diff --git a/src/Ryujinx.Gtk3/UI/RendererWidgetBase.cs b/src/Ryujinx.Gtk3/UI/RendererWidgetBase.cs
index 0e636792d..12139e87d 100644
--- a/src/Ryujinx.Gtk3/UI/RendererWidgetBase.cs
+++ b/src/Ryujinx.Gtk3/UI/RendererWidgetBase.cs
@@ -13,16 +13,13 @@ using Ryujinx.Input.HLE;
13using Ryujinx.UI.Common.Configuration; 13using Ryujinx.UI.Common.Configuration;
14using Ryujinx.UI.Common.Helper; 14using Ryujinx.UI.Common.Helper;
15using Ryujinx.UI.Widgets; 15using Ryujinx.UI.Widgets;
16using SixLabors.ImageSharp; 16using SkiaSharp;
17using SixLabors.ImageSharp.Formats.Png;
18using SixLabors.ImageSharp.PixelFormats;
19using SixLabors.ImageSharp.Processing;
20using System; 17using System;
21using System.Diagnostics; 18using System.Diagnostics;
22using System.IO; 19using System.IO;
20using System.Runtime.InteropServices;
23using System.Threading; 21using System.Threading;
24using System.Threading.Tasks; 22using System.Threading.Tasks;
25using Image = SixLabors.ImageSharp.Image;
26using Key = Ryujinx.Input.Key; 23using Key = Ryujinx.Input.Key;
27using ScalingFilter = Ryujinx.Graphics.GAL.ScalingFilter; 24using ScalingFilter = Ryujinx.Graphics.GAL.ScalingFilter;
28using Switch = Ryujinx.HLE.Switch; 25using Switch = Ryujinx.HLE.Switch;
@@ -404,23 +401,31 @@ namespace Ryujinx.UI
404 return; 401 return;
405 } 402 }
406 403
407 Image image = e.IsBgra ? Image.LoadPixelData<Bgra32>(e.Data, e.Width, e.Height) 404 var colorType = e.IsBgra ? SKColorType.Bgra8888 : SKColorType.Rgba8888;
408 : Image.LoadPixelData<Rgba32>(e.Data, e.Width, e.Height); 405 using var image = new SKBitmap(new SKImageInfo(e.Width, e.Height, colorType, SKAlphaType.Premul));
409 406
410 if (e.FlipX) 407 Marshal.Copy(e.Data, 0, image.GetPixels(), e.Data.Length);
411 { 408 using var surface = SKSurface.Create(image.Info);
412 image.Mutate(x => x.Flip(FlipMode.Horizontal)); 409 var canvas = surface.Canvas;
413 }
414 410
415 if (e.FlipY) 411 if (e.FlipX || e.FlipY)
416 { 412 {
417 image.Mutate(x => x.Flip(FlipMode.Vertical)); 413 canvas.Clear(SKColors.Transparent);
414
415 float scaleX = e.FlipX ? -1 : 1;
416 float scaleY = e.FlipY ? -1 : 1;
417
418 var matrix = SKMatrix.CreateScale(scaleX, scaleY, image.Width / 2f, image.Height / 2f);
419
420 canvas.SetMatrix(matrix);
418 } 421 }
422 canvas.DrawBitmap(image, new SKPoint());
419 423
420 image.SaveAsPng(path, new PngEncoder() 424 surface.Flush();
421 { 425 using var snapshot = surface.Snapshot();
422 ColorType = PngColorType.Rgb, 426 using var encoded = snapshot.Encode(SKEncodedImageFormat.Png, 80);
423 }); 427 using var file = File.OpenWrite(path);
428 encoded.SaveTo(file);
424 429
425 image.Dispose(); 430 image.Dispose();
426 431
diff --git a/src/Ryujinx.Gtk3/UI/Windows/AvatarWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/AvatarWindow.cs
index d9ecd47b7..fcd960df0 100644
--- a/src/Ryujinx.Gtk3/UI/Windows/AvatarWindow.cs
+++ b/src/Ryujinx.Gtk3/UI/Windows/AvatarWindow.cs
@@ -9,16 +9,13 @@ using LibHac.Tools.FsSystem.NcaUtils;
9using Ryujinx.Common.Memory; 9using Ryujinx.Common.Memory;
10using Ryujinx.HLE.FileSystem; 10using Ryujinx.HLE.FileSystem;
11using Ryujinx.UI.Common.Configuration; 11using Ryujinx.UI.Common.Configuration;
12using SixLabors.ImageSharp; 12using SkiaSharp;
13using SixLabors.ImageSharp.Formats.Png;
14using SixLabors.ImageSharp.PixelFormats;
15using SixLabors.ImageSharp.Processing;
16using System; 13using System;
17using System.Buffers.Binary; 14using System.Buffers.Binary;
18using System.Collections.Generic; 15using System.Collections.Generic;
19using System.IO; 16using System.IO;
20using System.Reflection; 17using System.Reflection;
21using Image = SixLabors.ImageSharp.Image; 18using System.Runtime.InteropServices;
22 19
23namespace Ryujinx.UI.Windows 20namespace Ryujinx.UI.Windows
24{ 21{
@@ -144,9 +141,11 @@ namespace Ryujinx.UI.Windows
144 141
145 stream.Position = 0; 142 stream.Position = 0;
146 143
147 Image avatarImage = Image.LoadPixelData<Rgba32>(DecompressYaz0(stream), 256, 256); 144 using var avatarImage = new SKBitmap(new SKImageInfo(256, 256, SKColorType.Rgba8888));
145 var data = DecompressYaz0(stream);
146 Marshal.Copy(data, 0, avatarImage.GetPixels(), data.Length);
148 147
149 avatarImage.SaveAsPng(streamPng); 148 avatarImage.Encode(streamPng, SKEncodedImageFormat.Png, 80);
150 149
151 _avatarDict.Add(item.FullPath, streamPng.ToArray()); 150 _avatarDict.Add(item.FullPath, streamPng.ToArray());
152 } 151 }
@@ -170,15 +169,23 @@ namespace Ryujinx.UI.Windows
170 { 169 {
171 using MemoryStream streamJpg = MemoryStreamManager.Shared.GetStream(); 170 using MemoryStream streamJpg = MemoryStreamManager.Shared.GetStream();
172 171
173 Image avatarImage = Image.Load(data, new PngDecoder()); 172 using var avatarImage = SKBitmap.Decode(data);
173 using var surface = SKSurface.Create(avatarImage.Info);
174 174
175 avatarImage.Mutate(x => x.BackgroundColor(new Rgba32( 175 var background = new SKColor(
176 (byte)(_backgroundColor.Red * 255), 176 (byte)(_backgroundColor.Red * 255),
177 (byte)(_backgroundColor.Green * 255), 177 (byte)(_backgroundColor.Green * 255),
178 (byte)(_backgroundColor.Blue * 255), 178 (byte)(_backgroundColor.Blue * 255),
179 (byte)(_backgroundColor.Alpha * 255) 179 (byte)(_backgroundColor.Alpha * 255)
180 ))); 180 );
181 avatarImage.SaveAsJpeg(streamJpg); 181 var canvas = surface.Canvas;
182 canvas.Clear(background);
183 canvas.DrawBitmap(avatarImage, new SKPoint());
184
185 surface.Flush();
186 using var snapshot = surface.Snapshot();
187 using var encoded = snapshot.Encode(SKEncodedImageFormat.Jpeg, 80);
188 encoded.SaveTo(streamJpg);
182 189
183 return streamJpg.ToArray(); 190 return streamJpg.ToArray();
184 } 191 }
diff --git a/src/Ryujinx.Gtk3/UI/Windows/UserProfilesManagerWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/UserProfilesManagerWindow.cs
index d1e5fa9fc..77afc5d1f 100644
--- a/src/Ryujinx.Gtk3/UI/Windows/UserProfilesManagerWindow.cs
+++ b/src/Ryujinx.Gtk3/UI/Windows/UserProfilesManagerWindow.cs
@@ -4,15 +4,13 @@ using Ryujinx.HLE.FileSystem;
4using Ryujinx.HLE.HOS.Services.Account.Acc; 4using Ryujinx.HLE.HOS.Services.Account.Acc;
5using Ryujinx.UI.Common.Configuration; 5using Ryujinx.UI.Common.Configuration;
6using Ryujinx.UI.Widgets; 6using Ryujinx.UI.Widgets;
7using SixLabors.ImageSharp; 7using SkiaSharp;
8using SixLabors.ImageSharp.Processing;
9using System; 8using System;
10using System.Collections.Generic; 9using System.Collections.Generic;
11using System.IO; 10using System.IO;
12using System.Reflection; 11using System.Reflection;
13using System.Threading; 12using System.Threading;
14using System.Threading.Tasks; 13using System.Threading.Tasks;
15using Image = SixLabors.ImageSharp.Image;
16 14
17namespace Ryujinx.UI.Windows 15namespace Ryujinx.UI.Windows
18{ 16{
@@ -177,13 +175,13 @@ namespace Ryujinx.UI.Windows
177 175
178 private void ProcessProfileImage(byte[] buffer) 176 private void ProcessProfileImage(byte[] buffer)
179 { 177 {
180 using Image image = Image.Load(buffer); 178 using var image = SKBitmap.Decode(buffer);
181 179
182 image.Mutate(x => x.Resize(256, 256)); 180 image.Resize(new SKImageInfo(256, 256), SKFilterQuality.High);
183 181
184 using MemoryStream streamJpg = MemoryStreamManager.Shared.GetStream(); 182 using MemoryStream streamJpg = MemoryStreamManager.Shared.GetStream();
185 183
186 image.SaveAsJpeg(streamJpg); 184 image.Encode(streamJpg, SKEncodedImageFormat.Jpeg, 80);
187 185
188 _bufferImageProfile = streamJpg.ToArray(); 186 _bufferImageProfile = streamJpg.ToArray();
189 } 187 }
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRenderer.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRenderer.cs
index 3f7516e6a..239535ad5 100644
--- a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRenderer.cs
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRenderer.cs
@@ -112,11 +112,16 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
112 { 112 {
113 // Update the parameters that were provided. 113 // Update the parameters that were provided.
114 _state.InputText = inputText ?? _state.InputText; 114 _state.InputText = inputText ?? _state.InputText;
115 _state.CursorBegin = cursorBegin.GetValueOrDefault(_state.CursorBegin); 115 _state.CursorBegin = Math.Max(0, cursorBegin.GetValueOrDefault(_state.CursorBegin));
116 _state.CursorEnd = cursorEnd.GetValueOrDefault(_state.CursorEnd); 116 _state.CursorEnd = Math.Min(cursorEnd.GetValueOrDefault(_state.CursorEnd), _state.InputText.Length);
117 _state.OverwriteMode = overwriteMode.GetValueOrDefault(_state.OverwriteMode); 117 _state.OverwriteMode = overwriteMode.GetValueOrDefault(_state.OverwriteMode);
118 _state.TypingEnabled = typingEnabled.GetValueOrDefault(_state.TypingEnabled); 118 _state.TypingEnabled = typingEnabled.GetValueOrDefault(_state.TypingEnabled);
119 119
120 var begin = _state.CursorBegin;
121 var end = _state.CursorEnd;
122 _state.CursorBegin = Math.Min(begin, end);
123 _state.CursorEnd = Math.Max(begin, end);
124
120 // Reset the cursor blink. 125 // Reset the cursor blink.
121 _state.TextBoxBlinkCounter = 0; 126 _state.TextBoxBlinkCounter = 0;
122 127
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs
index 9e48568e1..cc62eca1d 100644
--- a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs
@@ -1,14 +1,9 @@
1using Ryujinx.HLE.UI; 1using Ryujinx.HLE.UI;
2using Ryujinx.Memory; 2using Ryujinx.Memory;
3using SixLabors.Fonts; 3using SkiaSharp;
4using SixLabors.ImageSharp;
5using SixLabors.ImageSharp.Drawing.Processing;
6using SixLabors.ImageSharp.PixelFormats;
7using SixLabors.ImageSharp.Processing;
8using System; 4using System;
9using System.Diagnostics; 5using System.Diagnostics;
10using System.IO; 6using System.IO;
11using System.Numerics;
12using System.Reflection; 7using System.Reflection;
13using System.Runtime.InteropServices; 8using System.Runtime.InteropServices;
14 9
@@ -29,38 +24,39 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
29 private readonly object _bufferLock = new(); 24 private readonly object _bufferLock = new();
30 25
31 private RenderingSurfaceInfo _surfaceInfo = null; 26 private RenderingSurfaceInfo _surfaceInfo = null;
32 private Image<Argb32> _surface = null; 27 private SKImageInfo _imageInfo;
28 private SKSurface _surface = null;
33 private byte[] _bufferData = null; 29 private byte[] _bufferData = null;
34 30
35 private readonly Image _ryujinxLogo = null; 31 private readonly SKBitmap _ryujinxLogo = null;
36 private readonly Image _padAcceptIcon = null; 32 private readonly SKBitmap _padAcceptIcon = null;
37 private readonly Image _padCancelIcon = null; 33 private readonly SKBitmap _padCancelIcon = null;
38 private readonly Image _keyModeIcon = null; 34 private readonly SKBitmap _keyModeIcon = null;
39 35
40 private readonly float _textBoxOutlineWidth; 36 private readonly float _textBoxOutlineWidth;
41 private readonly float _padPressedPenWidth; 37 private readonly float _padPressedPenWidth;
42 38
43 private readonly Color _textNormalColor; 39 private readonly SKColor _textNormalColor;
44 private readonly Color _textSelectedColor; 40 private readonly SKColor _textSelectedColor;
45 private readonly Color _textOverCursorColor; 41 private readonly SKColor _textOverCursorColor;
46 42
47 private readonly Brush _panelBrush; 43 private readonly SKPaint _panelBrush;
48 private readonly Brush _disabledBrush; 44 private readonly SKPaint _disabledBrush;
49 private readonly Brush _cursorBrush; 45 private readonly SKPaint _cursorBrush;
50 private readonly Brush _selectionBoxBrush; 46 private readonly SKPaint _selectionBoxBrush;
51 47
52 private readonly Pen _textBoxOutlinePen; 48 private readonly SKPaint _textBoxOutlinePen;
53 private readonly Pen _cursorPen; 49 private readonly SKPaint _cursorPen;
54 private readonly Pen _selectionBoxPen; 50 private readonly SKPaint _selectionBoxPen;
55 private readonly Pen _padPressedPen; 51 private readonly SKPaint _padPressedPen;
56 52
57 private readonly int _inputTextFontSize; 53 private readonly int _inputTextFontSize;
58 private Font _messageFont; 54 private SKFont _messageFont;
59 private Font _inputTextFont; 55 private SKFont _inputTextFont;
60 private Font _labelsTextFont; 56 private SKFont _labelsTextFont;
61 57
62 private RectangleF _panelRectangle; 58 private SKRect _panelRectangle;
63 private Point _logoPosition; 59 private SKPoint _logoPosition;
64 private float _messagePositionY; 60 private float _messagePositionY;
65 61
66 public SoftwareKeyboardRendererBase(IHostUITheme uiTheme) 62 public SoftwareKeyboardRendererBase(IHostUITheme uiTheme)
@@ -78,10 +74,10 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
78 _padCancelIcon = LoadResource(typeof(SoftwareKeyboardRendererBase).Assembly, padCancelIconPath, 0, 0); 74 _padCancelIcon = LoadResource(typeof(SoftwareKeyboardRendererBase).Assembly, padCancelIconPath, 0, 0);
79 _keyModeIcon = LoadResource(typeof(SoftwareKeyboardRendererBase).Assembly, keyModeIconPath, 0, 0); 75 _keyModeIcon = LoadResource(typeof(SoftwareKeyboardRendererBase).Assembly, keyModeIconPath, 0, 0);
80 76
81 Color panelColor = ToColor(uiTheme.DefaultBackgroundColor, 255); 77 var panelColor = ToColor(uiTheme.DefaultBackgroundColor, 255);
82 Color panelTransparentColor = ToColor(uiTheme.DefaultBackgroundColor, 150); 78 var panelTransparentColor = ToColor(uiTheme.DefaultBackgroundColor, 150);
83 Color borderColor = ToColor(uiTheme.DefaultBorderColor); 79 var borderColor = ToColor(uiTheme.DefaultBorderColor);
84 Color selectionBackgroundColor = ToColor(uiTheme.SelectionBackgroundColor); 80 var selectionBackgroundColor = ToColor(uiTheme.SelectionBackgroundColor);
85 81
86 _textNormalColor = ToColor(uiTheme.DefaultForegroundColor); 82 _textNormalColor = ToColor(uiTheme.DefaultForegroundColor);
87 _textSelectedColor = ToColor(uiTheme.SelectionForegroundColor); 83 _textSelectedColor = ToColor(uiTheme.SelectionForegroundColor);
@@ -92,15 +88,29 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
92 _textBoxOutlineWidth = 2; 88 _textBoxOutlineWidth = 2;
93 _padPressedPenWidth = 2; 89 _padPressedPenWidth = 2;
94 90
95 _panelBrush = new SolidBrush(panelColor); 91 _panelBrush = new SKPaint()
96 _disabledBrush = new SolidBrush(panelTransparentColor); 92 {
97 _cursorBrush = new SolidBrush(_textNormalColor); 93 Color = panelColor,
98 _selectionBoxBrush = new SolidBrush(selectionBackgroundColor); 94 IsAntialias = true
95 };
96 _disabledBrush = new SKPaint()
97 {
98 Color = panelTransparentColor,
99 IsAntialias = true
100 };
101 _cursorBrush = new SKPaint() { Color = _textNormalColor, IsAntialias = true };
102 _selectionBoxBrush = new SKPaint() { Color = selectionBackgroundColor, IsAntialias = true };
99 103
100 _textBoxOutlinePen = Pens.Solid(borderColor, _textBoxOutlineWidth); 104 _textBoxOutlinePen = new SKPaint()
101 _cursorPen = Pens.Solid(_textNormalColor, cursorWidth); 105 {
102 _selectionBoxPen = Pens.Solid(selectionBackgroundColor, cursorWidth); 106 Color = borderColor,
103 _padPressedPen = Pens.Solid(borderColor, _padPressedPenWidth); 107 StrokeWidth = _textBoxOutlineWidth,
108 IsStroke = true,
109 IsAntialias = true
110 };
111 _cursorPen = new SKPaint() { Color = _textNormalColor, StrokeWidth = cursorWidth, IsStroke = true, IsAntialias = true };
112 _selectionBoxPen = new SKPaint() { Color = selectionBackgroundColor, StrokeWidth = cursorWidth, IsStroke = true, IsAntialias = true };
113 _padPressedPen = new SKPaint() { Color = borderColor, StrokeWidth = _padPressedPenWidth, IsStroke = true, IsAntialias = true };
104 114
105 _inputTextFontSize = 20; 115 _inputTextFontSize = 20;
106 116
@@ -123,9 +133,10 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
123 { 133 {
124 try 134 try
125 { 135 {
126 _messageFont = SystemFonts.CreateFont(fontFamily, 26, FontStyle.Regular); 136 using var typeface = SKTypeface.FromFamilyName(fontFamily, SKFontStyle.Normal);
127 _inputTextFont = SystemFonts.CreateFont(fontFamily, _inputTextFontSize, FontStyle.Regular); 137 _messageFont = new SKFont(typeface, 26);
128 _labelsTextFont = SystemFonts.CreateFont(fontFamily, 24, FontStyle.Regular); 138 _inputTextFont = new SKFont(typeface, _inputTextFontSize);
139 _labelsTextFont = new SKFont(typeface, 24);
129 140
130 return; 141 return;
131 } 142 }
@@ -137,7 +148,7 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
137 throw new Exception($"None of these fonts were found in the system: {String.Join(", ", availableFonts)}!"); 148 throw new Exception($"None of these fonts were found in the system: {String.Join(", ", availableFonts)}!");
138 } 149 }
139 150
140 private static Color ToColor(ThemeColor color, byte? overrideAlpha = null, bool flipRgb = false) 151 private static SKColor ToColor(ThemeColor color, byte? overrideAlpha = null, bool flipRgb = false)
141 { 152 {
142 var a = (byte)(color.A * 255); 153 var a = (byte)(color.A * 255);
143 var r = (byte)(color.R * 255); 154 var r = (byte)(color.R * 255);
@@ -151,34 +162,33 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
151 b = (byte)(255 - b); 162 b = (byte)(255 - b);
152 } 163 }
153 164
154 return Color.FromRgba(r, g, b, overrideAlpha.GetValueOrDefault(a)); 165 return new SKColor(r, g, b, overrideAlpha.GetValueOrDefault(a));
155 } 166 }
156 167
157 private static Image LoadResource(Assembly assembly, string resourcePath, int newWidth, int newHeight) 168 private static SKBitmap LoadResource(Assembly assembly, string resourcePath, int newWidth, int newHeight)
158 { 169 {
159 Stream resourceStream = assembly.GetManifestResourceStream(resourcePath); 170 Stream resourceStream = assembly.GetManifestResourceStream(resourcePath);
160 171
161 return LoadResource(resourceStream, newWidth, newHeight); 172 return LoadResource(resourceStream, newWidth, newHeight);
162 } 173 }
163 174
164 private static Image LoadResource(Stream resourceStream, int newWidth, int newHeight) 175 private static SKBitmap LoadResource(Stream resourceStream, int newWidth, int newHeight)
165 { 176 {
166 Debug.Assert(resourceStream != null); 177 Debug.Assert(resourceStream != null);
167 178
168 var image = Image.Load(resourceStream); 179 var bitmap = SKBitmap.Decode(resourceStream);
169 180
170 if (newHeight != 0 && newWidth != 0) 181 if (newHeight != 0 && newWidth != 0)
171 { 182 {
172 image.Mutate(x => x.Resize(newWidth, newHeight, KnownResamplers.Lanczos3)); 183 var resized = bitmap.Resize(new SKImageInfo(newWidth, newHeight), SKFilterQuality.High);
184 if (resized != null)
185 {
186 bitmap.Dispose();
187 bitmap = resized;
188 }
173 } 189 }
174 190
175 return image; 191 return bitmap;
176 }
177
178 private static void SetGraphicsOptions(IImageProcessingContext context)
179 {
180 context.GetGraphicsOptions().Antialias = true;
181 context.GetDrawingOptions().GraphicsOptions.Antialias = true;
182 } 192 }
183 193
184 private void DrawImmutableElements() 194 private void DrawImmutableElements()
@@ -187,22 +197,18 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
187 { 197 {
188 return; 198 return;
189 } 199 }
200 var canvas = _surface.Canvas;
190 201
191 _surface.Mutate(context => 202 canvas.Clear(SKColors.Transparent);
192 { 203 canvas.DrawRect(_panelRectangle, _panelBrush);
193 SetGraphicsOptions(context); 204 canvas.DrawBitmap(_ryujinxLogo, _logoPosition);
194
195 context.Clear(Color.Transparent);
196 context.Fill(_panelBrush, _panelRectangle);
197 context.DrawImage(_ryujinxLogo, _logoPosition, 1);
198 205
199 float halfWidth = _panelRectangle.Width / 2; 206 float halfWidth = _panelRectangle.Width / 2;
200 float buttonsY = _panelRectangle.Y + 185; 207 float buttonsY = _panelRectangle.Top + 185;
201 208
202 PointF disableButtonPosition = new(halfWidth + 180, buttonsY); 209 SKPoint disableButtonPosition = new(halfWidth + 180, buttonsY);
203 210
204 DrawControllerToggle(context, disableButtonPosition); 211 DrawControllerToggle(canvas, disableButtonPosition);
205 });
206 } 212 }
207 213
208 public void DrawMutableElements(SoftwareKeyboardUIState state) 214 public void DrawMutableElements(SoftwareKeyboardUIState state)
@@ -212,40 +218,43 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
212 return; 218 return;
213 } 219 }
214 220
215 _surface.Mutate(context => 221 using var paint = new SKPaint(_messageFont)
216 { 222 {
217 var messageRectangle = MeasureString(MessageText, _messageFont); 223 Color = _textNormalColor,
218 float messagePositionX = (_panelRectangle.Width - messageRectangle.Width) / 2 - messageRectangle.X; 224 IsAntialias = true
219 float messagePositionY = _messagePositionY - messageRectangle.Y; 225 };
220 var messagePosition = new PointF(messagePositionX, messagePositionY);
221 var messageBoundRectangle = new RectangleF(messagePositionX, messagePositionY, messageRectangle.Width, messageRectangle.Height);
222 226
223 SetGraphicsOptions(context); 227 var canvas = _surface.Canvas;
228 var messageRectangle = MeasureString(MessageText, paint);
229 float messagePositionX = (_panelRectangle.Width - messageRectangle.Width) / 2 - messageRectangle.Left;
230 float messagePositionY = _messagePositionY - messageRectangle.Top;
231 var messagePosition = new SKPoint(messagePositionX, messagePositionY);
232 var messageBoundRectangle = SKRect.Create(messagePositionX, messagePositionY, messageRectangle.Width, messageRectangle.Height);
224 233
225 context.Fill(_panelBrush, messageBoundRectangle); 234 canvas.DrawRect(messageBoundRectangle, _panelBrush);
226 235
227 context.DrawText(MessageText, _messageFont, _textNormalColor, messagePosition); 236 canvas.DrawText(MessageText, messagePosition.X, messagePosition.Y + _messageFont.Metrics.XHeight + _messageFont.Metrics.Descent, paint);
228 237
229 if (!state.TypingEnabled) 238 if (!state.TypingEnabled)
230 { 239 {
231 // Just draw a semi-transparent rectangle on top to fade the component with the background. 240 // Just draw a semi-transparent rectangle on top to fade the component with the background.
232 // TODO (caian): This will not work if one decides to add make background semi-transparent as well. 241 // TODO (caian): This will not work if one decides to add make background semi-transparent as well.
233 242
234 context.Fill(_disabledBrush, messageBoundRectangle); 243 canvas.DrawRect(messageBoundRectangle, _disabledBrush);
235 } 244 }
245
246 DrawTextBox(canvas, state);
236 247
237 DrawTextBox(context, state); 248 float halfWidth = _panelRectangle.Width / 2;
249 float buttonsY = _panelRectangle.Top + 185;
238 250
239 float halfWidth = _panelRectangle.Width / 2; 251 SKPoint acceptButtonPosition = new(halfWidth - 180, buttonsY);
240 float buttonsY = _panelRectangle.Y + 185; 252 SKPoint cancelButtonPosition = new(halfWidth, buttonsY);
253 SKPoint disableButtonPosition = new(halfWidth + 180, buttonsY);
241 254
242 PointF acceptButtonPosition = new(halfWidth - 180, buttonsY); 255 DrawPadButton(canvas, acceptButtonPosition, _padAcceptIcon, AcceptText, state.AcceptPressed, state.ControllerEnabled);
243 PointF cancelButtonPosition = new(halfWidth, buttonsY); 256 DrawPadButton(canvas, cancelButtonPosition, _padCancelIcon, CancelText, state.CancelPressed, state.ControllerEnabled);
244 PointF disableButtonPosition = new(halfWidth + 180, buttonsY);
245 257
246 DrawPadButton(context, acceptButtonPosition, _padAcceptIcon, AcceptText, state.AcceptPressed, state.ControllerEnabled);
247 DrawPadButton(context, cancelButtonPosition, _padCancelIcon, CancelText, state.CancelPressed, state.ControllerEnabled);
248 });
249 } 258 }
250 259
251 public void CreateSurface(RenderingSurfaceInfo surfaceInfo) 260 public void CreateSurface(RenderingSurfaceInfo surfaceInfo)
@@ -268,7 +277,8 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
268 Debug.Assert(_surfaceInfo.Height <= totalHeight); 277 Debug.Assert(_surfaceInfo.Height <= totalHeight);
269 Debug.Assert(_surfaceInfo.Pitch * _surfaceInfo.Height <= _surfaceInfo.Size); 278 Debug.Assert(_surfaceInfo.Pitch * _surfaceInfo.Height <= _surfaceInfo.Size);
270 279
271 _surface = new Image<Argb32>((int)totalWidth, (int)totalHeight); 280 _imageInfo = new SKImageInfo((int)totalWidth, (int)totalHeight, SKColorType.Rgba8888);
281 _surface = SKSurface.Create(_imageInfo);
272 282
273 ComputeConstants(); 283 ComputeConstants();
274 DrawImmutableElements(); 284 DrawImmutableElements();
@@ -282,76 +292,81 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
282 int panelHeight = 240; 292 int panelHeight = 240;
283 int panelPositionY = totalHeight - panelHeight; 293 int panelPositionY = totalHeight - panelHeight;
284 294
285 _panelRectangle = new RectangleF(0, panelPositionY, totalWidth, panelHeight); 295 _panelRectangle = SKRect.Create(0, panelPositionY, totalWidth, panelHeight);
286 296
287 _messagePositionY = panelPositionY + 60; 297 _messagePositionY = panelPositionY + 60;
288 298
289 int logoPositionX = (totalWidth - _ryujinxLogo.Width) / 2; 299 int logoPositionX = (totalWidth - _ryujinxLogo.Width) / 2;
290 int logoPositionY = panelPositionY + 18; 300 int logoPositionY = panelPositionY + 18;
291 301
292 _logoPosition = new Point(logoPositionX, logoPositionY); 302 _logoPosition = new SKPoint(logoPositionX, logoPositionY);
293 } 303 }
294 private static RectangleF MeasureString(string text, Font font) 304 private static SKRect MeasureString(string text, SKPaint paint)
295 { 305 {
296 TextOptions options = new(font); 306 SKRect bounds = SKRect.Empty;
297 307
298 if (text == "") 308 if (text == "")
299 { 309 {
300 FontRectangle emptyRectangle = TextMeasurer.MeasureSize(" ", options); 310 paint.MeasureText(" ", ref bounds);
301 311 }
302 return new RectangleF(0, emptyRectangle.Y, 0, emptyRectangle.Height); 312 else
313 {
314 paint.MeasureText(text, ref bounds);
303 } 315 }
304 316
305 FontRectangle rectangle = TextMeasurer.MeasureSize(text, options); 317 return bounds;
306
307 return new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
308 } 318 }
309 319
310 private static RectangleF MeasureString(ReadOnlySpan<char> text, Font font) 320 private static SKRect MeasureString(ReadOnlySpan<char> text, SKPaint paint)
311 { 321 {
312 TextOptions options = new(font); 322 SKRect bounds = SKRect.Empty;
313 323
314 if (text == "") 324 if (text == "")
315 { 325 {
316 FontRectangle emptyRectangle = TextMeasurer.MeasureSize(" ", options); 326 paint.MeasureText(" ", ref bounds);
317 return new RectangleF(0, emptyRectangle.Y, 0, emptyRectangle.Height); 327 }
328 else
329 {
330 paint.MeasureText(text, ref bounds);
318 } 331 }
319 332
320 FontRectangle rectangle = TextMeasurer.MeasureSize(text, options); 333 return bounds;
321
322 return new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
323 } 334 }
324 335
325 private void DrawTextBox(IImageProcessingContext context, SoftwareKeyboardUIState state) 336 private void DrawTextBox(SKCanvas canvas, SoftwareKeyboardUIState state)
326 { 337 {
327 var inputTextRectangle = MeasureString(state.InputText, _inputTextFont); 338 using var textPaint = new SKPaint(_labelsTextFont)
339 {
340 IsAntialias = true,
341 Color = _textNormalColor
342 };
343 var inputTextRectangle = MeasureString(state.InputText, textPaint);
328 344
329 float boxWidth = (int)(Math.Max(300, inputTextRectangle.Width + inputTextRectangle.X + 8)); 345 float boxWidth = (int)(Math.Max(300, inputTextRectangle.Width + inputTextRectangle.Left + 8));
330 float boxHeight = 32; 346 float boxHeight = 32;
331 float boxY = _panelRectangle.Y + 110; 347 float boxY = _panelRectangle.Top + 110;
332 float boxX = (int)((_panelRectangle.Width - boxWidth) / 2); 348 float boxX = (int)((_panelRectangle.Width - boxWidth) / 2);
333 349
334 RectangleF boxRectangle = new(boxX, boxY, boxWidth, boxHeight); 350 SKRect boxRectangle = SKRect.Create(boxX, boxY, boxWidth, boxHeight);
335 351
336 RectangleF boundRectangle = new(_panelRectangle.X, boxY - _textBoxOutlineWidth, 352 SKRect boundRectangle = SKRect.Create(_panelRectangle.Left, boxY - _textBoxOutlineWidth,
337 _panelRectangle.Width, boxHeight + 2 * _textBoxOutlineWidth); 353 _panelRectangle.Width, boxHeight + 2 * _textBoxOutlineWidth);
338 354
339 context.Fill(_panelBrush, boundRectangle); 355 canvas.DrawRect(boundRectangle, _panelBrush);
340 356
341 context.Draw(_textBoxOutlinePen, boxRectangle); 357 canvas.DrawRect(boxRectangle, _textBoxOutlinePen);
342 358
343 float inputTextX = (_panelRectangle.Width - inputTextRectangle.Width) / 2 - inputTextRectangle.X; 359 float inputTextX = (_panelRectangle.Width - inputTextRectangle.Width) / 2 - inputTextRectangle.Left;
344 float inputTextY = boxY + 5; 360 float inputTextY = boxY + 5;
345 361
346 var inputTextPosition = new PointF(inputTextX, inputTextY); 362 var inputTextPosition = new SKPoint(inputTextX, inputTextY);
347 363 canvas.DrawText(state.InputText, inputTextPosition.X, inputTextPosition.Y + (_labelsTextFont.Metrics.XHeight + _labelsTextFont.Metrics.Descent), textPaint);
348 context.DrawText(state.InputText, _inputTextFont, _textNormalColor, inputTextPosition);
349 364
350 // Draw the cursor on top of the text and redraw the text with a different color if necessary. 365 // Draw the cursor on top of the text and redraw the text with a different color if necessary.
351 366
352 Color cursorTextColor; 367 SKColor cursorTextColor;
353 Brush cursorBrush; 368 SKPaint cursorBrush;
354 Pen cursorPen; 369 SKPaint cursorPen;
355 370
356 float cursorPositionYTop = inputTextY + 1; 371 float cursorPositionYTop = inputTextY + 1;
357 float cursorPositionYBottom = cursorPositionYTop + _inputTextFontSize + 1; 372 float cursorPositionYBottom = cursorPositionYTop + _inputTextFontSize + 1;
@@ -371,12 +386,12 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
371 ReadOnlySpan<char> textUntilBegin = state.InputText.AsSpan(0, state.CursorBegin); 386 ReadOnlySpan<char> textUntilBegin = state.InputText.AsSpan(0, state.CursorBegin);
372 ReadOnlySpan<char> textUntilEnd = state.InputText.AsSpan(0, state.CursorEnd); 387 ReadOnlySpan<char> textUntilEnd = state.InputText.AsSpan(0, state.CursorEnd);
373 388
374 var selectionBeginRectangle = MeasureString(textUntilBegin, _inputTextFont); 389 var selectionBeginRectangle = MeasureString(textUntilBegin, textPaint);
375 var selectionEndRectangle = MeasureString(textUntilEnd, _inputTextFont); 390 var selectionEndRectangle = MeasureString(textUntilEnd, textPaint);
376 391
377 cursorVisible = true; 392 cursorVisible = true;
378 cursorPositionXLeft = inputTextX + selectionBeginRectangle.Width + selectionBeginRectangle.X; 393 cursorPositionXLeft = inputTextX + selectionBeginRectangle.Width + selectionBeginRectangle.Left;
379 cursorPositionXRight = inputTextX + selectionEndRectangle.Width + selectionEndRectangle.X; 394 cursorPositionXRight = inputTextX + selectionEndRectangle.Width + selectionEndRectangle.Left;
380 } 395 }
381 else 396 else
382 { 397 {
@@ -390,10 +405,10 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
390 405
391 int cursorBegin = Math.Min(state.InputText.Length, state.CursorBegin); 406 int cursorBegin = Math.Min(state.InputText.Length, state.CursorBegin);
392 ReadOnlySpan<char> textUntilCursor = state.InputText.AsSpan(0, cursorBegin); 407 ReadOnlySpan<char> textUntilCursor = state.InputText.AsSpan(0, cursorBegin);
393 var cursorTextRectangle = MeasureString(textUntilCursor, _inputTextFont); 408 var cursorTextRectangle = MeasureString(textUntilCursor, textPaint);
394 409
395 cursorVisible = true; 410 cursorVisible = true;
396 cursorPositionXLeft = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.X; 411 cursorPositionXLeft = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.Left;
397 412
398 if (state.OverwriteMode) 413 if (state.OverwriteMode)
399 { 414 {
@@ -402,8 +417,8 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
402 if (state.CursorBegin < state.InputText.Length) 417 if (state.CursorBegin < state.InputText.Length)
403 { 418 {
404 textUntilCursor = state.InputText.AsSpan(0, cursorBegin + 1); 419 textUntilCursor = state.InputText.AsSpan(0, cursorBegin + 1);
405 cursorTextRectangle = MeasureString(textUntilCursor, _inputTextFont); 420 cursorTextRectangle = MeasureString(textUntilCursor, textPaint);
406 cursorPositionXRight = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.X; 421 cursorPositionXRight = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.Left;
407 } 422 }
408 else 423 else
409 { 424 {
@@ -430,29 +445,32 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
430 445
431 if (cursorWidth == 0) 446 if (cursorWidth == 0)
432 { 447 {
433 PointF[] points = { 448 canvas.DrawLine(new SKPoint(cursorPositionXLeft, cursorPositionYTop),
434 new PointF(cursorPositionXLeft, cursorPositionYTop), 449 new SKPoint(cursorPositionXLeft, cursorPositionYBottom),
435 new PointF(cursorPositionXLeft, cursorPositionYBottom), 450 cursorPen);
436 };
437
438 context.DrawLine(cursorPen, points);
439 } 451 }
440 else 452 else
441 { 453 {
442 var cursorRectangle = new RectangleF(cursorPositionXLeft, cursorPositionYTop, cursorWidth, cursorHeight); 454 var cursorRectangle = SKRect.Create(cursorPositionXLeft, cursorPositionYTop, cursorWidth, cursorHeight);
455
456 canvas.DrawRect(cursorRectangle, cursorPen);
457 canvas.DrawRect(cursorRectangle, cursorBrush);
443 458
444 context.Draw(cursorPen, cursorRectangle); 459 using var textOverCursor = SKSurface.Create(new SKImageInfo((int)cursorRectangle.Width, (int)cursorRectangle.Height, SKColorType.Rgba8888));
445 context.Fill(cursorBrush, cursorRectangle); 460 var textOverCanvas = textOverCursor.Canvas;
461 var textRelativePosition = new SKPoint(inputTextPosition.X - cursorRectangle.Left, inputTextPosition.Y - cursorRectangle.Top);
446 462
447 Image<Argb32> textOverCursor = new((int)cursorRectangle.Width, (int)cursorRectangle.Height); 463 using var cursorPaint = new SKPaint(_inputTextFont)
448 textOverCursor.Mutate(context =>
449 { 464 {
450 var textRelativePosition = new PointF(inputTextPosition.X - cursorRectangle.X, inputTextPosition.Y - cursorRectangle.Y); 465 Color = cursorTextColor,
451 context.DrawText(state.InputText, _inputTextFont, cursorTextColor, textRelativePosition); 466 IsAntialias = true
452 }); 467 };
453 468
454 var cursorPosition = new Point((int)cursorRectangle.X, (int)cursorRectangle.Y); 469 textOverCanvas.DrawText(state.InputText, textRelativePosition.X, textRelativePosition.Y + _inputTextFont.Metrics.XHeight + _inputTextFont.Metrics.Descent, cursorPaint);
455 context.DrawImage(textOverCursor, cursorPosition, 1); 470
471 var cursorPosition = new SKPoint((int)cursorRectangle.Left, (int)cursorRectangle.Top);
472 textOverCursor.Flush();
473 canvas.DrawSurface(textOverCursor, cursorPosition);
456 } 474 }
457 } 475 }
458 else if (!state.TypingEnabled) 476 else if (!state.TypingEnabled)
@@ -460,11 +478,11 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
460 // Just draw a semi-transparent rectangle on top to fade the component with the background. 478 // Just draw a semi-transparent rectangle on top to fade the component with the background.
461 // TODO (caian): This will not work if one decides to add make background semi-transparent as well. 479 // TODO (caian): This will not work if one decides to add make background semi-transparent as well.
462 480
463 context.Fill(_disabledBrush, boundRectangle); 481 canvas.DrawRect(boundRectangle, _disabledBrush);
464 } 482 }
465 } 483 }
466 484
467 private void DrawPadButton(IImageProcessingContext context, PointF point, Image icon, string label, bool pressed, bool enabled) 485 private void DrawPadButton(SKCanvas canvas, SKPoint point, SKBitmap icon, string label, bool pressed, bool enabled)
468 { 486 {
469 // Use relative positions so we can center the entire drawing later. 487 // Use relative positions so we can center the entire drawing later.
470 488
@@ -473,12 +491,18 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
473 float iconWidth = icon.Width; 491 float iconWidth = icon.Width;
474 float iconHeight = icon.Height; 492 float iconHeight = icon.Height;
475 493
476 var labelRectangle = MeasureString(label, _labelsTextFont); 494 using var paint = new SKPaint(_labelsTextFont)
495 {
496 Color = _textNormalColor,
497 IsAntialias = true
498 };
499
500 var labelRectangle = MeasureString(label, paint);
477 501
478 float labelPositionX = iconWidth + 8 - labelRectangle.X; 502 float labelPositionX = iconWidth + 8 - labelRectangle.Left;
479 float labelPositionY = 3; 503 float labelPositionY = 3;
480 504
481 float fullWidth = labelPositionX + labelRectangle.Width + labelRectangle.X; 505 float fullWidth = labelPositionX + labelRectangle.Width + labelRectangle.Left;
482 float fullHeight = iconHeight; 506 float fullHeight = iconHeight;
483 507
484 // Convert all relative positions into absolute. 508 // Convert all relative positions into absolute.
@@ -489,24 +513,24 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
489 iconX += originX; 513 iconX += originX;
490 iconY += originY; 514 iconY += originY;
491 515
492 var iconPosition = new Point((int)iconX, (int)iconY); 516 var iconPosition = new SKPoint((int)iconX, (int)iconY);
493 var labelPosition = new PointF(labelPositionX + originX, labelPositionY + originY); 517 var labelPosition = new SKPoint(labelPositionX + originX, labelPositionY + originY);
494 518
495 var selectedRectangle = new RectangleF(originX - 2 * _padPressedPenWidth, originY - 2 * _padPressedPenWidth, 519 var selectedRectangle = SKRect.Create(originX - 2 * _padPressedPenWidth, originY - 2 * _padPressedPenWidth,
496 fullWidth + 4 * _padPressedPenWidth, fullHeight + 4 * _padPressedPenWidth); 520 fullWidth + 4 * _padPressedPenWidth, fullHeight + 4 * _padPressedPenWidth);
497 521
498 var boundRectangle = new RectangleF(originX, originY, fullWidth, fullHeight); 522 var boundRectangle = SKRect.Create(originX, originY, fullWidth, fullHeight);
499 boundRectangle.Inflate(4 * _padPressedPenWidth, 4 * _padPressedPenWidth); 523 boundRectangle.Inflate(4 * _padPressedPenWidth, 4 * _padPressedPenWidth);
500 524
501 context.Fill(_panelBrush, boundRectangle); 525 canvas.DrawRect(boundRectangle, _panelBrush);
502 context.DrawImage(icon, iconPosition, 1); 526 canvas.DrawBitmap(icon, iconPosition);
503 context.DrawText(label, _labelsTextFont, _textNormalColor, labelPosition); 527 canvas.DrawText(label, labelPosition.X, labelPosition.Y + _labelsTextFont.Metrics.XHeight + _labelsTextFont.Metrics.Descent, paint);
504 528
505 if (enabled) 529 if (enabled)
506 { 530 {
507 if (pressed) 531 if (pressed)
508 { 532 {
509 context.Draw(_padPressedPen, selectedRectangle); 533 canvas.DrawRect(selectedRectangle, _padPressedPen);
510 } 534 }
511 } 535 }
512 else 536 else
@@ -514,21 +538,26 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
514 // Just draw a semi-transparent rectangle on top to fade the component with the background. 538 // Just draw a semi-transparent rectangle on top to fade the component with the background.
515 // TODO (caian): This will not work if one decides to add make background semi-transparent as well. 539 // TODO (caian): This will not work if one decides to add make background semi-transparent as well.
516 540
517 context.Fill(_disabledBrush, boundRectangle); 541 canvas.DrawRect(boundRectangle, _disabledBrush);
518 } 542 }
519 } 543 }
520 544
521 private void DrawControllerToggle(IImageProcessingContext context, PointF point) 545 private void DrawControllerToggle(SKCanvas canvas, SKPoint point)
522 { 546 {
523 var labelRectangle = MeasureString(ControllerToggleText, _labelsTextFont); 547 using var paint = new SKPaint(_labelsTextFont)
548 {
549 IsAntialias = true,
550 Color = _textNormalColor
551 };
552 var labelRectangle = MeasureString(ControllerToggleText, paint);
524 553
525 // Use relative positions so we can center the entire drawing later. 554 // Use relative positions so we can center the entire drawing later.
526 555
527 float keyWidth = _keyModeIcon.Width; 556 float keyWidth = _keyModeIcon.Width;
528 float keyHeight = _keyModeIcon.Height; 557 float keyHeight = _keyModeIcon.Height;
529 558
530 float labelPositionX = keyWidth + 8 - labelRectangle.X; 559 float labelPositionX = keyWidth + 8 - labelRectangle.Left;
531 float labelPositionY = -labelRectangle.Y - 1; 560 float labelPositionY = -labelRectangle.Top - 1;
532 561
533 float keyX = 0; 562 float keyX = 0;
534 float keyY = (int)((labelPositionY + labelRectangle.Height - keyHeight) / 2); 563 float keyY = (int)((labelPositionY + labelRectangle.Height - keyHeight) / 2);
@@ -544,14 +573,14 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
544 keyX += originX; 573 keyX += originX;
545 keyY += originY; 574 keyY += originY;
546 575
547 var labelPosition = new PointF(labelPositionX + originX, labelPositionY + originY); 576 var labelPosition = new SKPoint(labelPositionX + originX, labelPositionY + originY);
548 var overlayPosition = new Point((int)keyX, (int)keyY); 577 var overlayPosition = new SKPoint((int)keyX, (int)keyY);
549 578
550 context.DrawImage(_keyModeIcon, overlayPosition, 1); 579 canvas.DrawBitmap(_keyModeIcon, overlayPosition);
551 context.DrawText(ControllerToggleText, _labelsTextFont, _textNormalColor, labelPosition); 580 canvas.DrawText(ControllerToggleText, labelPosition.X, labelPosition.Y + _labelsTextFont.Metrics.XHeight, paint);
552 } 581 }
553 582
554 public void CopyImageToBuffer() 583 public unsafe void CopyImageToBuffer()
555 { 584 {
556 lock (_bufferLock) 585 lock (_bufferLock)
557 { 586 {
@@ -561,21 +590,20 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
561 } 590 }
562 591
563 // Convert the pixel format used in the image to the one used in the Switch surface. 592 // Convert the pixel format used in the image to the one used in the Switch surface.
593 _surface.Flush();
564 594
565 if (!_surface.DangerousTryGetSinglePixelMemory(out Memory<Argb32> pixels)) 595 var buffer = new byte[_imageInfo.BytesSize];
596 fixed (byte* bufferPtr = buffer)
566 { 597 {
567 return; 598 if (!_surface.ReadPixels(_imageInfo, (nint)bufferPtr, _imageInfo.RowBytes, 0, 0))
599 {
600 return;
601 }
568 } 602 }
569 603
570 _bufferData = MemoryMarshal.AsBytes(pixels.Span).ToArray(); 604 _bufferData = buffer;
571 Span<uint> dataConvert = MemoryMarshal.Cast<byte, uint>(_bufferData);
572 605
573 Debug.Assert(_bufferData.Length == _surfaceInfo.Size); 606 Debug.Assert(buffer.Length == _surfaceInfo.Size);
574
575 for (int i = 0; i < dataConvert.Length; i++)
576 {
577 dataConvert[i] = BitOperations.RotateRight(dataConvert[i], 8);
578 }
579 } 607 }
580 } 608 }
581 609
diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/CaptureManager.cs b/src/Ryujinx.HLE/HOS/Services/Caps/CaptureManager.cs
index 91a8958e6..bf0c7e9dc 100644
--- a/src/Ryujinx.HLE/HOS/Services/Caps/CaptureManager.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Caps/CaptureManager.cs
@@ -1,10 +1,10 @@
1using Ryujinx.Common.Memory; 1using Ryujinx.Common.Memory;
2using Ryujinx.HLE.HOS.Services.Caps.Types; 2using Ryujinx.HLE.HOS.Services.Caps.Types;
3using SixLabors.ImageSharp; 3using SkiaSharp;
4using SixLabors.ImageSharp.PixelFormats;
5using System; 4using System;
6using System.IO; 5using System.IO;
7using System.Runtime.CompilerServices; 6using System.Runtime.CompilerServices;
7using System.Runtime.InteropServices;
8using System.Security.Cryptography; 8using System.Security.Cryptography;
9 9
10namespace Ryujinx.HLE.HOS.Services.Caps 10namespace Ryujinx.HLE.HOS.Services.Caps
@@ -118,7 +118,11 @@ namespace Ryujinx.HLE.HOS.Services.Caps
118 } 118 }
119 119
120 // NOTE: The saved JPEG file doesn't have the limitation in the extra EXIF data. 120 // NOTE: The saved JPEG file doesn't have the limitation in the extra EXIF data.
121 Image.LoadPixelData<Rgba32>(screenshotData, 1280, 720).SaveAsJpegAsync(filePath); 121 using var bitmap = new SKBitmap(new SKImageInfo(1280, 720, SKColorType.Rgba8888));
122 Marshal.Copy(screenshotData, 0, bitmap.GetPixels(), screenshotData.Length);
123 using var data = bitmap.Encode(SKEncodedImageFormat.Jpeg, 80);
124 using var file = File.OpenWrite(filePath);
125 data.SaveTo(file);
122 126
123 return ResultCode.Success; 127 return ResultCode.Success;
124 } 128 }
diff --git a/src/Ryujinx.HLE/Ryujinx.HLE.csproj b/src/Ryujinx.HLE/Ryujinx.HLE.csproj
index 0fcf9e4b5..83a11d4e0 100644
--- a/src/Ryujinx.HLE/Ryujinx.HLE.csproj
+++ b/src/Ryujinx.HLE/Ryujinx.HLE.csproj
@@ -2,6 +2,7 @@
2 2
3 <PropertyGroup> 3 <PropertyGroup>
4 <TargetFramework>net8.0</TargetFramework> 4 <TargetFramework>net8.0</TargetFramework>
5 <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
5 </PropertyGroup> 6 </PropertyGroup>
6 7
7 <ItemGroup> 8 <ItemGroup>
@@ -24,8 +25,8 @@
24 <PackageReference Include="LibHac" /> 25 <PackageReference Include="LibHac" />
25 <PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" /> 26 <PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" />
26 <PackageReference Include="MsgPack.Cli" /> 27 <PackageReference Include="MsgPack.Cli" />
27 <PackageReference Include="SixLabors.ImageSharp" /> 28 <PackageReference Include="SkiaSharp" />
28 <PackageReference Include="SixLabors.ImageSharp.Drawing" /> 29 <PackageReference Include="SkiaSharp.NativeAssets.Linux" />
29 <PackageReference Include="NetCoreServer" /> 30 <PackageReference Include="NetCoreServer" />
30 </ItemGroup> 31 </ItemGroup>
31 32
diff --git a/src/Ryujinx.UI.Common/Helper/ShortcutHelper.cs b/src/Ryujinx.UI.Common/Helper/ShortcutHelper.cs
index 58bdc90e6..1849f40cb 100644
--- a/src/Ryujinx.UI.Common/Helper/ShortcutHelper.cs
+++ b/src/Ryujinx.UI.Common/Helper/ShortcutHelper.cs
@@ -1,10 +1,7 @@
1using Ryujinx.Common; 1using Ryujinx.Common;
2using Ryujinx.Common.Configuration; 2using Ryujinx.Common.Configuration;
3using ShellLink; 3using ShellLink;
4using SixLabors.ImageSharp; 4using SkiaSharp;
5using SixLabors.ImageSharp.Formats.Png;
6using SixLabors.ImageSharp.PixelFormats;
7using SixLabors.ImageSharp.Processing;
8using System; 5using System;
9using System.Collections.Generic; 6using System.Collections.Generic;
10using System.IO; 7using System.IO;
@@ -21,8 +18,8 @@ namespace Ryujinx.UI.Common.Helper
21 iconPath += ".ico"; 18 iconPath += ".ico";
22 19
23 MemoryStream iconDataStream = new(iconData); 20 MemoryStream iconDataStream = new(iconData);
24 var image = Image.Load(iconDataStream); 21 using var image = SKBitmap.Decode(iconDataStream);
25 image.Mutate(x => x.Resize(128, 128)); 22 image.Resize(new SKImageInfo(128, 128), SKFilterQuality.High);
26 SaveBitmapAsIcon(image, iconPath); 23 SaveBitmapAsIcon(image, iconPath);
27 24
28 var shortcut = Shortcut.CreateShortcut(basePath, GetArgsString(applicationFilePath, applicationId), iconPath, 0); 25 var shortcut = Shortcut.CreateShortcut(basePath, GetArgsString(applicationFilePath, applicationId), iconPath, 0);
@@ -37,8 +34,10 @@ namespace Ryujinx.UI.Common.Helper
37 var desktopFile = EmbeddedResources.ReadAllText("Ryujinx.UI.Common/shortcut-template.desktop"); 34 var desktopFile = EmbeddedResources.ReadAllText("Ryujinx.UI.Common/shortcut-template.desktop");
38 iconPath += ".png"; 35 iconPath += ".png";
39 36
40 var image = Image.Load<Rgba32>(iconData); 37 var image = SKBitmap.Decode(iconData);
41 image.SaveAsPng(iconPath); 38 using var data = image.Encode(SKEncodedImageFormat.Png, 100);
39 using var file = File.OpenWrite(iconPath);
40 data.SaveTo(file);
42 41
43 using StreamWriter outputFile = new(Path.Combine(desktopPath, cleanedAppName + ".desktop")); 42 using StreamWriter outputFile = new(Path.Combine(desktopPath, cleanedAppName + ".desktop"));
44 outputFile.Write(desktopFile, cleanedAppName, iconPath, $"{basePath} {GetArgsString(applicationFilePath, applicationId)}"); 43 outputFile.Write(desktopFile, cleanedAppName, iconPath, $"{basePath} {GetArgsString(applicationFilePath, applicationId)}");
@@ -78,8 +77,10 @@ namespace Ryujinx.UI.Common.Helper
78 } 77 }
79 78
80 const string IconName = "icon.png"; 79 const string IconName = "icon.png";
81 var image = Image.Load<Rgba32>(iconData); 80 var image = SKBitmap.Decode(iconData);
82 image.SaveAsPng(Path.Combine(resourceFolderPath, IconName)); 81 using var data = image.Encode(SKEncodedImageFormat.Png, 100);
82 using var file = File.OpenWrite(Path.Combine(resourceFolderPath, IconName));
83 data.SaveTo(file);
83 84
84 // plist file 85 // plist file
85 using StreamWriter outputFile = new(Path.Combine(contentFolderPath, "Info.plist")); 86 using StreamWriter outputFile = new(Path.Combine(contentFolderPath, "Info.plist"));
@@ -148,7 +149,7 @@ namespace Ryujinx.UI.Common.Helper
148 /// <param name="source">The source bitmap image that will be saved as an .ico file</param> 149 /// <param name="source">The source bitmap image that will be saved as an .ico file</param>
149 /// <param name="filePath">The location that the new .ico file will be saved too (Make sure to include '.ico' in the path).</param> 150 /// <param name="filePath">The location that the new .ico file will be saved too (Make sure to include '.ico' in the path).</param>
150 [SupportedOSPlatform("windows")] 151 [SupportedOSPlatform("windows")]
151 private static void SaveBitmapAsIcon(Image source, string filePath) 152 private static void SaveBitmapAsIcon(SKBitmap source, string filePath)
152 { 153 {
153 // Code Modified From https://stackoverflow.com/a/11448060/368354 by Benlitz 154 // Code Modified From https://stackoverflow.com/a/11448060/368354 by Benlitz
154 byte[] header = { 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 32, 0, 0, 0, 0, 0, 22, 0, 0, 0 }; 155 byte[] header = { 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 32, 0, 0, 0, 0, 0, 22, 0, 0, 0 };
@@ -156,13 +157,16 @@ namespace Ryujinx.UI.Common.Helper
156 157
157 fs.Write(header); 158 fs.Write(header);
158 // Writing actual data 159 // Writing actual data
159 source.Save(fs, PngFormat.Instance); 160 using var data = source.Encode(SKEncodedImageFormat.Png, 100);
161 data.SaveTo(fs);
160 // Getting data length (file length minus header) 162 // Getting data length (file length minus header)
161 long dataLength = fs.Length - header.Length; 163 long dataLength = fs.Length - header.Length;
162 // Write it in the correct place 164 // Write it in the correct place
163 fs.Seek(14, SeekOrigin.Begin); 165 fs.Seek(14, SeekOrigin.Begin);
164 fs.WriteByte((byte)dataLength); 166 fs.WriteByte((byte)dataLength);
165 fs.WriteByte((byte)(dataLength >> 8)); 167 fs.WriteByte((byte)(dataLength >> 8));
168 fs.WriteByte((byte)(dataLength >> 16));
169 fs.WriteByte((byte)(dataLength >> 24));
166 } 170 }
167 } 171 }
168} 172}
diff --git a/src/Ryujinx/UI/Applet/AvaloniaDynamicTextInputHandler.cs b/src/Ryujinx/UI/Applet/AvaloniaDynamicTextInputHandler.cs
index 531d00611..0e7cfb8e6 100644
--- a/src/Ryujinx/UI/Applet/AvaloniaDynamicTextInputHandler.cs
+++ b/src/Ryujinx/UI/Applet/AvaloniaDynamicTextInputHandler.cs
@@ -41,17 +41,12 @@ namespace Ryujinx.Ava.UI.Applet
41 41
42 private void TextChanged(string text) 42 private void TextChanged(string text)
43 { 43 {
44 TextChangedEvent?.Invoke(text ?? string.Empty, _hiddenTextBox.SelectionStart, _hiddenTextBox.SelectionEnd, true); 44 TextChangedEvent?.Invoke(text ?? string.Empty, _hiddenTextBox.SelectionStart, _hiddenTextBox.SelectionEnd, false);
45 } 45 }
46 46
47 private void SelectionChanged(int selection) 47 private void SelectionChanged(int selection)
48 { 48 {
49 if (_hiddenTextBox.SelectionEnd < _hiddenTextBox.SelectionStart) 49 TextChangedEvent?.Invoke(_hiddenTextBox.Text ?? string.Empty, _hiddenTextBox.SelectionStart, _hiddenTextBox.SelectionEnd, false);
50 {
51 _hiddenTextBox.SelectionStart = _hiddenTextBox.SelectionEnd;
52 }
53
54 TextChangedEvent?.Invoke(_hiddenTextBox.Text ?? string.Empty, _hiddenTextBox.SelectionStart, _hiddenTextBox.SelectionEnd, true);
55 } 50 }
56 51
57 private void AvaloniaDynamicTextInputHandler_TextInput(object sender, string text) 52 private void AvaloniaDynamicTextInputHandler_TextInput(object sender, string text)
diff --git a/src/Ryujinx/UI/Helpers/OffscreenTextBox.cs b/src/Ryujinx/UI/Helpers/OffscreenTextBox.cs
index a055f3353..dd736037e 100644
--- a/src/Ryujinx/UI/Helpers/OffscreenTextBox.cs
+++ b/src/Ryujinx/UI/Helpers/OffscreenTextBox.cs
@@ -1,11 +1,14 @@
1using Avalonia.Controls; 1using Avalonia.Controls;
2using Avalonia.Input; 2using Avalonia.Input;
3using Avalonia.Interactivity; 3using Avalonia.Interactivity;
4using System;
4 5
5namespace Ryujinx.Ava.UI.Helpers 6namespace Ryujinx.Ava.UI.Helpers
6{ 7{
7 public class OffscreenTextBox : TextBox 8 public class OffscreenTextBox : TextBox
8 { 9 {
10 protected override Type StyleKeyOverride => typeof(TextBox);
11
9 public static RoutedEvent<KeyEventArgs> GetKeyDownRoutedEvent() 12 public static RoutedEvent<KeyEventArgs> GetKeyDownRoutedEvent()
10 { 13 {
11 return KeyDownEvent; 14 return KeyDownEvent;
diff --git a/src/Ryujinx/UI/Windows/MainWindow.axaml b/src/Ryujinx/UI/Windows/MainWindow.axaml
index 6c2042f93..3a2e02c26 100644
--- a/src/Ryujinx/UI/Windows/MainWindow.axaml
+++ b/src/Ryujinx/UI/Windows/MainWindow.axaml
@@ -42,12 +42,10 @@
42 </Window.KeyBindings> 42 </Window.KeyBindings>
43 <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> 43 <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
44 <Grid.RowDefinitions> 44 <Grid.RowDefinitions>
45 <RowDefinition Height="Auto" />
46 <RowDefinition Height="*" /> 45 <RowDefinition Height="*" />
47 </Grid.RowDefinitions> 46 </Grid.RowDefinitions>
48 <helpers:OffscreenTextBox Name="HiddenTextBox" Grid.Row="0" /> 47 <helpers:OffscreenTextBox IsEnabled="False" Opacity="0" Name="HiddenTextBox" IsHitTestVisible="False" IsTabStop="False" />
49 <Grid 48 <Grid
50 Grid.Row="1"
51 HorizontalAlignment="Stretch" 49 HorizontalAlignment="Stretch"
52 VerticalAlignment="Stretch"> 50 VerticalAlignment="Stretch">
53 <Grid.ColumnDefinitions> 51 <Grid.ColumnDefinitions>