diff --git a/v2rayN/ServiceLib/Common/QRCodeHelper.cs b/v2rayN/ServiceLib/Common/QRCodeHelper.cs
index 82d6125e..7a024a3e 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 @@ namespace ServiceLib.Common
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 18065310..e19a4d4a 100644
--- a/v2rayN/ServiceLib/Enums/EViewAction.cs
+++ b/v2rayN/ServiceLib/Enums/EViewAction.cs
@@ -15,6 +15,7 @@
ShareServer,
ShowHideWindow,
ScanScreenTask,
+ ScanImageTask,
Shutdown,
BrowseServer,
ImportRulesFromFile,
diff --git a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs
index fe2fec2e..b5508c71 100644
--- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs
+++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs
@@ -681,6 +681,15 @@ namespace ServiceLib.Resx {
}
}
+ ///
+ /// 查找类似 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 f8d7b39a..636ad527 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 99c3226e..f6ba0dc5 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 e345d2c6..08c31baa 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 26187ec7..ea0604fe 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 c4c292fb..68c75d17 100644
--- a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs
+++ b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs
@@ -25,6 +25,7 @@ namespace ServiceLib.ViewModels
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 @@ namespace ServiceLib.ViewModels
//Presets
public ReactiveCommand RegionalPresetDefaultCmd { get; }
+
public ReactiveCommand RegionalPresetRussiaCmd { get; }
public ReactiveCommand ReloadCmd { get; }
@@ -121,7 +123,11 @@ namespace ServiceLib.ViewModels
});
AddServerViaScanCmd = ReactiveCommand.CreateFromTask(async () =>
{
- await AddServerViaScanTaskAsync();
+ await AddServerViaScanAsync();
+ });
+ AddServerViaImageCmd = ReactiveCommand.CreateFromTask(async () =>
+ {
+ await AddServerViaImageAsync();
});
//Subscription
@@ -386,12 +392,34 @@ namespace ServiceLib.ViewModels
}
}
- 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 @@ namespace ServiceLib.ViewModels
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 9e3dab2e..74701048 100644
--- a/v2rayN/ServiceLib/ViewModels/StatusBarViewModel.cs
+++ b/v2rayN/ServiceLib/ViewModels/StatusBarViewModel.cs
@@ -211,7 +211,7 @@ namespace ServiceLib.ViewModels
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 d490767e..6dd66d40 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 2e965ab1..8c468f75 100644
--- a/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs
+++ b/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs
@@ -4,6 +4,7 @@ using Avalonia.Controls.ApplicationLifetimes;
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 @@ namespace v2rayN.Desktop.Views
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 @@ namespace v2rayN.Desktop.Views
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 @@ namespace v2rayN.Desktop.Views
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 aefd7619..f4a03452 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 cad39dba..ff8c8833 100644
--- a/v2rayN/v2rayN/Common/QRCodeHelper.cs
+++ b/v2rayN/v2rayN/Common/QRCodeHelper.cs
@@ -4,10 +4,6 @@ using System.Windows;
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 @@ namespace v2rayN
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 @@ namespace v2rayN
}
}
- 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);
- return new(96 / g.DpiX, 96 / g.DpiY);
+ x = 96 / g.DpiX;
+ y = 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 86503e46..79d2ffbe 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}" />
+