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}" />
+