Skip to content

Commit 687ea68

Browse files
committed
feat(Avalonia): add clipboard/file support to OCR Tool
1 parent 50a0e40 commit 687ea68

3 files changed

Lines changed: 273 additions & 75 deletions

File tree

SnapX.Avalonia/Views/OCR.axaml

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,55 @@
153153
HorizontalAlignment="Right"
154154
Orientation="Horizontal"
155155
Spacing="10">
156+
<SplitButton
157+
Click="OcrClipboard_Click"
158+
Content="Paste from Clipboard"
159+
ToolTip.Tip="Only supports one file/paste at once.">
160+
<SplitButton.Flyout>
161+
<MenuFlyout Placement="BottomEdgeAlignedRight">
162+
<MenuItem Click="OcrFile_Click" Header="Select Image File...">
163+
<MenuItem.Icon>
164+
<controls:SymbolIcon Symbol="OpenFile" />
165+
</MenuItem.Icon>
166+
</MenuItem>
167+
</MenuFlyout>
168+
</SplitButton.Flyout>
169+
</SplitButton>
156170
<Button Click="CopyResult_Click" Content="Copy" />
157-
<Button Click="SelectRegion_Click" Content="Select region" />
171+
<SplitButton
172+
Click="SelectRegion_Click"
173+
Name="RegionSplitButton"
174+
Tag="Default">
175+
<SplitButton.Flyout>
176+
<MenuFlyout Placement="BottomEdgeAlignedRight">
177+
<MenuItem
178+
Click="SelectRegionDelay_Click"
179+
CommandParameter="Default"
180+
Header="Select with Configured Delay">
181+
<MenuItem.Icon>
182+
<controls:SymbolIcon Symbol="Clock" />
183+
</MenuItem.Icon>
184+
</MenuItem>
185+
<Separator />
186+
<MenuItem
187+
Click="SelectRegionDelay_Click"
188+
CommandParameter="3"
189+
Header="3 Second Delay" />
190+
<MenuItem
191+
Click="SelectRegionDelay_Click"
192+
CommandParameter="5"
193+
Header="5 Second Delay" />
194+
<MenuItem
195+
Click="SelectRegionDelay_Click"
196+
CommandParameter="10"
197+
Header="10 Second Delay" />
198+
</MenuFlyout>
199+
</SplitButton.Flyout>
200+
<StackPanel Orientation="Horizontal" Spacing="8">
201+
<fluent:SymbolIcon Name="RegionIcon" Symbol="ScreenCut" />
202+
<TextBlock Name="RegionText" Text="Select region" />
203+
</StackPanel>
204+
</SplitButton>
158205
</StackPanel>
159206

160207
</Grid>

SnapX.Avalonia/Views/OCR.axaml.cs

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@
22
using Avalonia.Controls;
33
using Avalonia.Controls.Primitives;
44
using Avalonia.Input;
5+
using Avalonia.Input.Platform;
56
using Avalonia.Interactivity;
67
using Avalonia.Media;
78
using Avalonia.Media.Imaging;
89
using Avalonia.Media.TextFormatting;
10+
using Avalonia.Platform.Storage;
911
using Avalonia.Styling;
12+
using FluentAvalonia.UI.Controls;
1013
using FluentAvalonia.UI.Windowing;
1114
using SixLabors.ImageSharp;
15+
using SixLabors.ImageSharp.PixelFormats;
1216
using SixLabors.ImageSharp.Processing;
1317
using SnapX.Avalonia.ViewModels;
1418
using SnapX.Core;
@@ -228,6 +232,7 @@ private async Task RunOCRAsync(string languageCode, CancellationToken cts = defa
228232
catch (Exception ex)
229233
{
230234
ResultText?.Text = ex.ToString();
235+
OnToggleView(ShowTextBtn, new RoutedEventArgs());
231236
DebugHelper.Logger?.Error(ex.ToString());
232237
}
233238
finally
@@ -359,4 +364,148 @@ private void SingleLine_OnTapped(object? Sender, TappedEventArgs E)
359364
ResultText.Text = ResultText.Text?.Replace("\r", "").Replace("\n", "");
360365
}
361366
}
367+
368+
private async void OcrClipboard_Click(object? Sender, RoutedEventArgs E)
369+
{
370+
Stream? ms = null;
371+
try
372+
{
373+
var clipboard = TopLevel.GetTopLevel(this)?.Clipboard;
374+
if (clipboard is null) return;
375+
376+
using var data = await clipboard.TryGetDataAsync();
377+
Bitmap? bitmap = null;
378+
if (data != null)
379+
{
380+
bitmap = await data.TryGetValueAsync(DataFormat.Bitmap);
381+
}
382+
else
383+
{
384+
DebugHelper.WriteAlways("OCR clipboard did not get back data");
385+
await new ContentDialog { Title = "Error", Content = "Clipboard is empty.", CloseButtonText = "OK" }.ShowAsync(this);
386+
return;
387+
}
388+
389+
if (bitmap is null)
390+
{
391+
DebugHelper.WriteAlways("OCR clipboard did not get back a Bitmap");
392+
var file = await data.TryGetValueAsync(DataFormat.File);
393+
if (file is null)
394+
{
395+
DebugHelper.WriteAlways("OCR clipboard did not get an image file");
396+
await new ContentDialog { Title = "Error", Content = "No image or file found on clipboard.", CloseButtonText = "OK" }.ShowAsync(this);
397+
return;
398+
}
399+
400+
ms = new FileStream(file.Path.LocalPath, FileMode.Open, FileAccess.Read);
401+
}
402+
else
403+
{
404+
ms = new MemoryStream();
405+
bitmap.Save(ms);
406+
bitmap.Dispose();
407+
ms.Position = 0;
408+
}
409+
410+
_img = await SixLabors.ImageSharp.Image.LoadAsync<Rgba32>(ms);
411+
412+
if (LanguageSelector?.SelectedIndex is not (>= 0 and var index))
413+
return;
414+
415+
Title = $"OCR Result for clipboard image {_img.Metadata.DecodedImageFormat?.Name}";
416+
var code = _ocrViewModel.GetLanguageCode(index);
417+
await RunOCRAsync(code);
418+
}
419+
catch (Exception ex)
420+
{
421+
DebugHelper.WriteAlways($"OCR Error: {ex.Message}");
422+
await new ContentDialog { Title = "OCR Error", Content = ex.Message, CloseButtonText = "OK" }.ShowAsync(this);
423+
}
424+
finally
425+
{
426+
if (ms is not null) await ms.DisposeAsync();
427+
}
428+
}
429+
430+
private async void OcrFile_Click(object? Sender, RoutedEventArgs E)
431+
{
432+
Stream? ms = null;
433+
try
434+
{
435+
var topLevel = TopLevel.GetTopLevel(this);
436+
if (topLevel is null) return;
437+
438+
var files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
439+
{
440+
Title = "Select Image for OCR",
441+
AllowMultiple = false,
442+
FileTypeFilter = [FilePickerFileTypes.ImageAll]
443+
});
444+
445+
var file = files.FirstOrDefault();
446+
if (file is null) return;
447+
448+
ms = await file.OpenReadAsync();
449+
_img = await SixLabors.ImageSharp.Image.LoadAsync<Rgba32>(ms);
450+
451+
if (LanguageSelector?.SelectedIndex is not (>= 0 and var index))
452+
return;
453+
454+
Title = $"OCR Result for file {_img.Metadata.DecodedImageFormat?.Name}";
455+
var code = _ocrViewModel.GetLanguageCode(index);
456+
await RunOCRAsync(code);
457+
}
458+
catch (Exception ex)
459+
{
460+
DebugHelper.WriteAlways($"OCR File Error: {ex.Message}");
461+
await new ContentDialog
462+
{
463+
Title = "OCR Error",
464+
Content = ex.Message,
465+
CloseButtonText = "OK"
466+
}.ShowAsync(this);
467+
}
468+
finally
469+
{
470+
if (ms is not null) await ms.DisposeAsync();
471+
}
472+
}
473+
private async void SelectRegionDelay_Click(object? Sender, RoutedEventArgs E)
474+
{
475+
try
476+
{
477+
if (Sender is MenuItem { CommandParameter: string param } source)
478+
{
479+
var delay = param == "Default" ? (int)App.SnapX.GetConfiguration().DefaultTaskSettings.CaptureSettings.ScreenshotDelay : int.Parse(param);
480+
await Task.Delay(TimeSpan.FromSeconds(delay));
481+
RegionSplitButton.Tag = delay;
482+
483+
if (param == "Default")
484+
{
485+
RegionText.Text = "Select region";
486+
RegionIcon.Symbol = FluentIcons.Common.Symbol.ScreenCut;
487+
source.Click += OcrClipboard_Click;
488+
}
489+
else
490+
{
491+
RegionText.Text = $"Select region ({delay}s Delay)";
492+
RegionIcon.Symbol = FluentIcons.Common.Symbol.Clock;
493+
source.Click += SelectRegionDelay_Click;
494+
}
495+
SelectRegion_Click(Sender, E);
496+
497+
}
498+
}
499+
catch (Exception ex)
500+
{
501+
DebugHelper.WriteAlways($"Delay Selection Error: {ex.Message}");
502+
await new ContentDialog
503+
{
504+
Title = "Selection Error",
505+
Content = ex.Message,
506+
CloseButtonText = "OK"
507+
}.ShowAsync(this);
508+
}
509+
}
510+
362511
}

0 commit comments

Comments
 (0)