From 5c0fba8744982b3d3830900fc1c075c7dff230df Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Fri, 18 Oct 2024 17:35:32 +0800 Subject: [PATCH] Add scanning QR code from image --- v2rayN/ServiceLib/Common/QRCodeHelper.cs | 75 +++++++++++++ v2rayN/ServiceLib/Enums/EViewAction.cs | 1 + v2rayN/ServiceLib/Resx/ResUI.Designer.cs | 9 ++ v2rayN/ServiceLib/Resx/ResUI.resx | 3 + v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx | 3 + v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx | 3 + v2rayN/ServiceLib/ServiceLib.csproj | 4 +- .../ViewModels/MainWindowViewModel.cs | 36 ++++++- .../ViewModels/StatusBarViewModel.cs | 2 +- v2rayN/v2rayN.Desktop/Views/MainWindow.axaml | 6 +- .../v2rayN.Desktop/Views/MainWindow.axaml.cs | 17 ++- v2rayN/v2rayN.Desktop/Views/QrcodeView.axaml | 5 +- v2rayN/v2rayN/Common/QRCodeHelper.cs | 102 ++++++------------ v2rayN/v2rayN/Views/MainWindow.xaml | 4 + v2rayN/v2rayN/Views/MainWindow.xaml.cs | 26 ++++- v2rayN/v2rayN/v2rayN.csproj | 1 - 16 files changed, 208 insertions(+), 89 deletions(-) diff --git a/v2rayN/ServiceLib/Common/QRCodeHelper.cs b/v2rayN/ServiceLib/Common/QRCodeHelper.cs index 82d6125ef1..7a024a3ece 100644 --- a/v2rayN/ServiceLib/Common/QRCodeHelper.cs +++ b/v2rayN/ServiceLib/Common/QRCodeHelper.cs @@ -1,4 +1,6 @@ using QRCoder; +using SkiaSharp; +using ZXing.SkiaSharp; namespace ServiceLib.Common { @@ -11,5 +13,78 @@ public class QRCodeHelper using PngByteQRCode qrCode = new(qrCodeData); return qrCode.GetGraphic(20); } + + public static string? ParseBarcode(string? fileName) + { + if (fileName == null || !File.Exists(fileName)) + { + return null; + } + + try + { + var image = SKImage.FromEncodedData(fileName); + var bitmap = SKBitmap.FromImage(image); + + return ReaderBarcode(bitmap); + } + catch + { + // ignored + } + + return null; + } + + public static string? ParseBarcode(byte[]? bytes) + { + try + { + var bitmap = SKBitmap.Decode(bytes); + //using var stream = new FileStream("test2.png", FileMode.Create, FileAccess.Write); + //using var image = SKImage.FromBitmap(bitmap); + //using var encodedImage = image.Encode(); + //encodedImage.SaveTo(stream); + return ReaderBarcode(bitmap); + } + catch + { + // ignored + } + + return null; + } + + private static string? ReaderBarcode(SKBitmap? bitmap) + { + var reader = new BarcodeReader(); + var result = reader.Decode(bitmap); + + if (result != null && Utils.IsNotEmpty(result.Text)) + { + return result.Text; + } + + //FlipBitmap + var result2 = reader.Decode(FlipBitmap(bitmap)); + return result2?.Text; + } + + private static SKBitmap FlipBitmap(SKBitmap bmp) + { + // Create a bitmap (to return) + var flipped = new SKBitmap(bmp.Width, bmp.Height, bmp.Info.ColorType, bmp.Info.AlphaType); + + // Create a canvas to draw into the bitmap + using var canvas = new SKCanvas(flipped); + + // Set a transform matrix which moves the bitmap to the right, + // and then "scales" it by -1, which just flips the pixels + // horizontally + canvas.Translate(bmp.Width, 0); + canvas.Scale(-1, 1); + canvas.DrawBitmap(bmp, 0, 0); + return flipped; + } } } \ No newline at end of file diff --git a/v2rayN/ServiceLib/Enums/EViewAction.cs b/v2rayN/ServiceLib/Enums/EViewAction.cs index 18065310be..e19a4d4a2c 100644 --- a/v2rayN/ServiceLib/Enums/EViewAction.cs +++ b/v2rayN/ServiceLib/Enums/EViewAction.cs @@ -15,6 +15,7 @@ public enum EViewAction ShareServer, ShowHideWindow, ScanScreenTask, + ScanImageTask, Shutdown, BrowseServer, ImportRulesFromFile, diff --git a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index fe2fec2e4c..b5508c7101 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -681,6 +681,15 @@ public static string menuAddServerViaClipboard { } } + /// + /// 查找类似 Scan QR code in the image 的本地化字符串。 + /// + public static string menuAddServerViaImage { + get { + return ResourceManager.GetString("menuAddServerViaImage", resourceCulture); + } + } + /// /// 查找类似 Scan QR code on the screen (Ctrl+S) 的本地化字符串。 /// diff --git a/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayN/ServiceLib/Resx/ResUI.resx index f8d7b39a58..636ad52725 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1351,4 +1351,7 @@ Users in China region can ignore this item + + Scan QR code in the image + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx index 99c3226ed4..f6ba0dc5ae 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -1348,4 +1348,7 @@ 俄罗斯 + + 扫描图片中的二维码 + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx index e345d2c644..08c31baa9d 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -1228,4 +1228,7 @@ 俄羅斯 + + 掃描圖片中的二維碼 + \ No newline at end of file diff --git a/v2rayN/ServiceLib/ServiceLib.csproj b/v2rayN/ServiceLib/ServiceLib.csproj index 26187ec7ef..ea0604fec1 100644 --- a/v2rayN/ServiceLib/ServiceLib.csproj +++ b/v2rayN/ServiceLib/ServiceLib.csproj @@ -16,7 +16,9 @@ - + + + diff --git a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs index c4c292fbc9..68c75d177e 100644 --- a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs @@ -25,6 +25,7 @@ public class MainWindowViewModel : MyReactiveObject public ReactiveCommand AddCustomServerCmd { get; } public ReactiveCommand AddServerViaClipboardCmd { get; } public ReactiveCommand AddServerViaScanCmd { get; } + public ReactiveCommand AddServerViaImageCmd { get; } //Subscription public ReactiveCommand SubSettingCmd { get; } @@ -46,6 +47,7 @@ public class MainWindowViewModel : MyReactiveObject //Presets public ReactiveCommand RegionalPresetDefaultCmd { get; } + public ReactiveCommand RegionalPresetRussiaCmd { get; } public ReactiveCommand ReloadCmd { get; } @@ -121,7 +123,11 @@ public MainWindowViewModel(Func>? updateView) }); AddServerViaScanCmd = ReactiveCommand.CreateFromTask(async () => { - await AddServerViaScanTaskAsync(); + await AddServerViaScanAsync(); + }); + AddServerViaImageCmd = ReactiveCommand.CreateFromTask(async () => + { + await AddServerViaImageAsync(); }); //Subscription @@ -386,12 +392,34 @@ public async Task AddServerViaClipboardAsync(string? clipboardData) } } - public async Task AddServerViaScanTaskAsync() + public async Task AddServerViaScanAsync() { _updateView?.Invoke(EViewAction.ScanScreenTask, null); } - public void ScanScreenResult(string result) + public async Task ScanScreenResult(byte[]? bytes) + { + var result = QRCodeHelper.ParseBarcode(bytes); + await AddScanResultAsync(result); + } + + public async Task AddServerViaImageAsync() + { + _updateView?.Invoke(EViewAction.ScanImageTask, null); + } + + public async Task ScanImageResult(string fileName) + { + if (Utils.IsNullOrEmpty(fileName)) + { + return; + } + + var result = QRCodeHelper.ParseBarcode(fileName); + await AddScanResultAsync(result); + } + + private async Task AddScanResultAsync(string? result) { if (Utils.IsNullOrEmpty(result)) { @@ -571,6 +599,6 @@ public async Task ApplyRegionalPreset(EPresetType type) Reload(); } - #endregion + #endregion Presets } } \ No newline at end of file diff --git a/v2rayN/ServiceLib/ViewModels/StatusBarViewModel.cs b/v2rayN/ServiceLib/ViewModels/StatusBarViewModel.cs index 9e3dab2e57..7470104852 100644 --- a/v2rayN/ServiceLib/ViewModels/StatusBarViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/StatusBarViewModel.cs @@ -211,7 +211,7 @@ private async Task AddServerViaClipboard() private async Task AddServerViaScan() { var service = Locator.Current.GetService(); - if (service != null) await service.AddServerViaScanTaskAsync(); + if (service != null) await service.AddServerViaScanAsync(); } private async Task UpdateSubscriptionProcess(bool blProxy) diff --git a/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml b/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml index d490767e91..6dd66d4035 100644 --- a/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml @@ -32,10 +32,8 @@ - + + diff --git a/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs b/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs index 2e965ab14a..8c468f75ae 100644 --- a/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs @@ -4,6 +4,7 @@ using Avalonia.Controls.Notifications; using Avalonia.Input; using Avalonia.Interactivity; +using Avalonia.Platform.Storage; using Avalonia.ReactiveUI; using Avalonia.Threading; using DialogHostAvalonia; @@ -60,6 +61,7 @@ public MainWindow() this.BindCommand(ViewModel, vm => vm.AddCustomServerCmd, v => v.menuAddCustomServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddServerViaClipboardCmd, v => v.menuAddServerViaClipboard).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddServerViaScanCmd, v => v.menuAddServerViaScan).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.AddServerViaImageCmd, v => v.menuAddServerViaImage).DisposeWith(disposables); //sub this.BindCommand(ViewModel, vm => vm.SubSettingCmd, v => v.menuSubSetting).DisposeWith(disposables); @@ -224,6 +226,10 @@ private async Task UpdateViewHandler(EViewAction action, object? obj) await ScanScreenTaskAsync(); break; + case EViewAction.ScanImageTask: + await ScanImageTaskAsync(); + break; + case EViewAction.AddServerViaClipboard: var clipboardData = await AvaUtils.GetClipboardData(this); ViewModel?.AddServerViaClipboardAsync(clipboardData); @@ -324,7 +330,16 @@ public async Task ScanScreenTaskAsync() ShowHideWindow(true); - //ViewModel?.ScanScreenTaskAsync(result); + //ViewModel?.ScanScreenResult(result); + } + private async Task ScanImageTaskAsync() + { + var fileName = await UI.OpenFileDialog(this,null ); + if (fileName.IsNullOrEmpty()) + { + return; + } + await ViewModel?.ScanImageResult(fileName); } private void MenuCheckUpdate_Click(object? sender, RoutedEventArgs e) diff --git a/v2rayN/v2rayN.Desktop/Views/QrcodeView.axaml b/v2rayN/v2rayN.Desktop/Views/QrcodeView.axaml index aefd76193d..f4a03452e1 100644 --- a/v2rayN/v2rayN.Desktop/Views/QrcodeView.axaml +++ b/v2rayN/v2rayN.Desktop/Views/QrcodeView.axaml @@ -17,8 +17,7 @@ + Height="300" /> - + \ No newline at end of file diff --git a/v2rayN/v2rayN/Common/QRCodeHelper.cs b/v2rayN/v2rayN/Common/QRCodeHelper.cs index cad39dbae4..ff8c883306 100644 --- a/v2rayN/v2rayN/Common/QRCodeHelper.cs +++ b/v2rayN/v2rayN/Common/QRCodeHelper.cs @@ -4,10 +4,6 @@ using System.Windows.Interop; using System.Windows.Media; using System.Windows.Media.Imaging; -using ZXing; -using ZXing.Common; -using ZXing.QrCode; -using ZXing.Windows.Compatibility; namespace v2rayN { @@ -25,11 +21,7 @@ public class QRCodeHelper try { var qrCodeImage = ServiceLib.Common.QRCodeHelper.GenQRCode(strContent); - if (qrCodeImage is null) - { - return null; - } - return ByteToImage(qrCodeImage); + return qrCodeImage is null ? null : ByteToImage(qrCodeImage); } catch { @@ -37,82 +29,54 @@ public class QRCodeHelper } } - private static ImageSource ByteToImage(byte[] imageData) - { - BitmapImage biImg = new(); - MemoryStream ms = new(imageData); - biImg.BeginInit(); - biImg.StreamSource = ms; - biImg.EndInit(); - - ImageSource imgSrc = biImg as ImageSource; - - return imgSrc; - } - - public static string ScanScreen(float dpiX, float dpiY) + public static byte[]? CaptureScreen(Window window) { try { + GetDpi(window, out var dpiX, out var dpiY); + var left = (int)(SystemParameters.WorkArea.Left); var top = (int)(SystemParameters.WorkArea.Top); var width = (int)(SystemParameters.WorkArea.Width / dpiX); var height = (int)(SystemParameters.WorkArea.Height / dpiY); - using Bitmap fullImage = new Bitmap(width, height); - using (Graphics g = Graphics.FromImage(fullImage)) - { - g.CopyFromScreen(left, top, 0, 0, fullImage.Size, CopyPixelOperation.SourceCopy); - } - int maxTry = 10; - for (int i = 0; i < maxTry; i++) - { - int marginLeft = (int)((double)fullImage.Width * i / 2.5 / maxTry); - int marginTop = (int)((double)fullImage.Height * i / 2.5 / maxTry); - Rectangle cropRect = new(marginLeft, marginTop, fullImage.Width - marginLeft * 2, fullImage.Height - marginTop * 2); - Bitmap target = new(width, height); + using var fullImage = new Bitmap(width, height); + using var g = Graphics.FromImage(fullImage); - double imageScale = (double)width / (double)cropRect.Width; - using (Graphics g = Graphics.FromImage(target)) - { - g.DrawImage(fullImage, new Rectangle(0, 0, target.Width, target.Height), - cropRect, - GraphicsUnit.Pixel); - } - - BitmapLuminanceSource source = new(target); - QRCodeReader reader = new(); - - BinaryBitmap bitmap = new(new HybridBinarizer(source)); - var result = reader.decode(bitmap); - if (result != null) - { - return result.Text; - } - else - { - BinaryBitmap bitmap2 = new(new HybridBinarizer(source.invert())); - var result2 = reader.decode(bitmap2); - if (result2 != null) - { - return result2.Text; - } - } - } + g.CopyFromScreen(left, top, 0, 0, fullImage.Size, CopyPixelOperation.SourceCopy); + //fullImage.Save("test1.png", ImageFormat.Png); + return ImageToByte(fullImage); } - catch (Exception ex) + catch { - Logging.SaveLog(ex.Message, ex); + return null; } - return string.Empty; } - public static Tuple GetDpiXY(Window window) + private static void GetDpi(Window window, out float x, out float y) { - IntPtr hWnd = new WindowInteropHelper(window).EnsureHandle(); - Graphics g = Graphics.FromHwnd(hWnd); + var hWnd = new WindowInteropHelper(window).EnsureHandle(); + var g = Graphics.FromHwnd(hWnd); + + x = 96 / g.DpiX; + y = 96 / g.DpiY; + } - return new(96 / g.DpiX, 96 / g.DpiY); + private static ImageSource ByteToImage(byte[] imageData) + { + BitmapImage biImg = new(); + using MemoryStream ms = new(imageData); + biImg.BeginInit(); + biImg.StreamSource = ms; + biImg.EndInit(); + + return biImg as ImageSource; + } + + private static byte[]? ImageToByte(Image img) + { + var converter = new ImageConverter(); + return converter.ConvertTo(img, typeof(byte[])) as byte[]; } } } \ No newline at end of file diff --git a/v2rayN/v2rayN/Views/MainWindow.xaml b/v2rayN/v2rayN/Views/MainWindow.xaml index 86503e461a..79d2ffbe3d 100644 --- a/v2rayN/v2rayN/Views/MainWindow.xaml +++ b/v2rayN/v2rayN/Views/MainWindow.xaml @@ -64,6 +64,10 @@ x:Name="menuAddServerViaScan" Height="{StaticResource MenuItemHeight}" Header="{x:Static resx:ResUI.menuAddServerViaScan}" /> + vm.AddCustomServerCmd, v => v.menuAddCustomServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddServerViaClipboardCmd, v => v.menuAddServerViaClipboard).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddServerViaScanCmd, v => v.menuAddServerViaScan).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.AddServerViaImageCmd, v => v.menuAddServerViaImage).DisposeWith(disposables); //sub this.BindCommand(ViewModel, vm => vm.SubSettingCmd, v => v.menuSubSetting).DisposeWith(disposables); @@ -221,6 +222,10 @@ private async Task UpdateViewHandler(EViewAction action, object? obj) await ScanScreenTaskAsync(); break; + case EViewAction.ScanImageTask: + await ScanImageTaskAsync(); + break; + case EViewAction.AddServerViaClipboard: var clipboardData = WindowsUtils.GetClipboardData(); ViewModel?.AddServerViaClipboardAsync(clipboardData); @@ -317,15 +322,26 @@ private async Task ScanScreenTaskAsync() { ShowHideWindow(false); - var dpiXY = QRCodeHelper.GetDpiXY(Application.Current.MainWindow); - string result = await Task.Run(() => + if (Application.Current?.MainWindow is Window window) { - return QRCodeHelper.ScanScreen(dpiXY.Item1, dpiXY.Item2); - }); + var bytes = QRCodeHelper.CaptureScreen(window); + await ViewModel?.ScanScreenResult(bytes); + } ShowHideWindow(true); + } - ViewModel?.ScanScreenResult(result); + private async Task ScanImageTaskAsync() + { + if (UI.OpenFileDialog(out var fileName, "PNG|*.png|All|*.*") != true) + { + return; + } + if (fileName.IsNullOrEmpty()) + { + return; + } + await ViewModel?.ScanImageResult(fileName); } private void MenuCheckUpdate_Click(object sender, RoutedEventArgs e) diff --git a/v2rayN/v2rayN/v2rayN.csproj b/v2rayN/v2rayN/v2rayN.csproj index a0bc58a922..b8cd8e3aaf 100644 --- a/v2rayN/v2rayN/v2rayN.csproj +++ b/v2rayN/v2rayN/v2rayN.csproj @@ -17,7 +17,6 @@ -