diff options
Diffstat (limited to 'src/Ryujinx.HLE/HOS')
3 files changed, 223 insertions, 186 deletions
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 @@ | |||
1 | using Ryujinx.HLE.UI; | 1 | using Ryujinx.HLE.UI; |
2 | using Ryujinx.Memory; | 2 | using Ryujinx.Memory; |
3 | using SixLabors.Fonts; | 3 | using SkiaSharp; |
4 | using SixLabors.ImageSharp; | ||
5 | using SixLabors.ImageSharp.Drawing.Processing; | ||
6 | using SixLabors.ImageSharp.PixelFormats; | ||
7 | using SixLabors.ImageSharp.Processing; | ||
8 | using System; | 4 | using System; |
9 | using System.Diagnostics; | 5 | using System.Diagnostics; |
10 | using System.IO; | 6 | using System.IO; |
11 | using System.Numerics; | ||
12 | using System.Reflection; | 7 | using System.Reflection; |
13 | using System.Runtime.InteropServices; | 8 | using 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 @@ | |||
1 | using Ryujinx.Common.Memory; | 1 | using Ryujinx.Common.Memory; |
2 | using Ryujinx.HLE.HOS.Services.Caps.Types; | 2 | using Ryujinx.HLE.HOS.Services.Caps.Types; |
3 | using SixLabors.ImageSharp; | 3 | using SkiaSharp; |
4 | using SixLabors.ImageSharp.PixelFormats; | ||
5 | using System; | 4 | using System; |
6 | using System.IO; | 5 | using System.IO; |
7 | using System.Runtime.CompilerServices; | 6 | using System.Runtime.CompilerServices; |
7 | using System.Runtime.InteropServices; | ||
8 | using System.Security.Cryptography; | 8 | using System.Security.Cryptography; |
9 | 9 | ||
10 | namespace Ryujinx.HLE.HOS.Services.Caps | 10 | namespace 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 | } |