├── .gitignore
├── Capture.Vision.Maui.Example
├── Resources
│ ├── Fonts
│ │ ├── OpenSans-Regular.ttf
│ │ └── OpenSans-Semibold.ttf
│ ├── AppIcon
│ │ ├── appicon.svg
│ │ └── appiconfg.svg
│ ├── Raw
│ │ └── AboutAssets.txt
│ ├── Splash
│ │ └── splash.svg
│ ├── Styles
│ │ ├── Colors.xaml
│ │ └── Styles.xaml
│ └── Images
│ │ └── dotnet_bot.svg
├── Properties
│ └── launchSettings.json
├── AppShell.xaml.cs
├── Platforms
│ ├── Android
│ │ ├── Resources
│ │ │ └── values
│ │ │ │ └── colors.xml
│ │ ├── MainApplication.cs
│ │ ├── AndroidManifest.xml
│ │ └── MainActivity.cs
│ ├── iOS
│ │ ├── AppDelegate.cs
│ │ ├── Program.cs
│ │ └── Info.plist
│ ├── MacCatalyst
│ │ ├── AppDelegate.cs
│ │ ├── Program.cs
│ │ └── Info.plist
│ ├── Windows
│ │ ├── App.xaml
│ │ ├── app.manifest
│ │ ├── App.xaml.cs
│ │ └── Package.appxmanifest
│ └── Tizen
│ │ ├── Main.cs
│ │ └── tizen-manifest.xml
├── App.xaml.cs
├── README.md
├── AppShell.xaml
├── MainPage.xaml
├── App.xaml
├── MauiProgram.cs
├── Capture.Vision.Maui.Example.csproj.user
├── MainPage.xaml.cs
├── Capture.Vision.Maui.Example.sln
├── CameraPage.xaml
├── Capture.Vision.Maui.Example.csproj
└── CameraPage.xaml.cs
├── Capture.Vision.Maui
├── BarcodeResult.cs
├── AppBuilderExtensions.cs
├── CameraInfo.cs
├── Capture.Vision.Maui.sln
├── DocumenResult.cs
├── CameraViewHandler.cs
├── Capture.Vision.Maui.csproj
├── MrzResult.cs
├── CameraView.cs
└── Platforms
│ ├── iOS
│ └── NativeCameraView.cs
│ ├── Windows
│ └── NativeCameraView.cs
│ └── Android
│ └── NativeCameraView.cs
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | /.vs
2 | obj
3 | bin
4 | .vs
5 |
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/Resources/Fonts/OpenSans-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yushulx/Capture-Vision-Maui/HEAD/Capture.Vision.Maui.Example/Resources/Fonts/OpenSans-Regular.ttf
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/Resources/Fonts/OpenSans-Semibold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yushulx/Capture-Vision-Maui/HEAD/Capture.Vision.Maui.Example/Resources/Fonts/OpenSans-Semibold.ttf
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "Windows Machine": {
4 | "commandName": "MsixPackage",
5 | "nativeDebugging": false
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/AppShell.xaml.cs:
--------------------------------------------------------------------------------
1 | namespace Capture.Vision.Maui.Example
2 | {
3 | public partial class AppShell : Shell
4 | {
5 | public AppShell()
6 | {
7 | InitializeComponent();
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/Resources/AppIcon/appicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/Platforms/Android/Resources/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #512BD4
4 | #2B0B98
5 | #2B0B98
6 |
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/App.xaml.cs:
--------------------------------------------------------------------------------
1 | namespace Capture.Vision.Maui.Example
2 | {
3 | public partial class App : Application
4 | {
5 | public App()
6 | {
7 | InitializeComponent();
8 |
9 | MainPage = new AppShell();
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/Platforms/iOS/AppDelegate.cs:
--------------------------------------------------------------------------------
1 | using Foundation;
2 |
3 | namespace Capture.Vision.Maui.Example
4 | {
5 | [Register("AppDelegate")]
6 | public class AppDelegate : MauiUIApplicationDelegate
7 | {
8 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
9 | }
10 | }
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/Platforms/MacCatalyst/AppDelegate.cs:
--------------------------------------------------------------------------------
1 | using Foundation;
2 |
3 | namespace Capture.Vision.Maui.Example
4 | {
5 | [Register("AppDelegate")]
6 | public class AppDelegate : MauiUIApplicationDelegate
7 | {
8 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
9 | }
10 | }
--------------------------------------------------------------------------------
/Capture.Vision.Maui/BarcodeResult.cs:
--------------------------------------------------------------------------------
1 | namespace Capture.Vision.Maui
2 | {
3 | public class BarcodeResult
4 | {
5 | public string Text { get; set; }
6 |
7 | public int[] Points { get; set; }
8 |
9 | public string Format1 { get; set; }
10 |
11 | public string Format2 { get; set; }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/README.md:
--------------------------------------------------------------------------------
1 | # .NET MAUI Barcode, Document, MRZ Scanner
2 |
3 | **Windows**
4 |
5 | https://github.com/yushulx/Capture-Vision-Maui/assets/2202306/63022269-a862-44a3-9217-bc6a9a2866be
6 |
7 | **Android**
8 |
9 | https://github.com/yushulx/Capture-Vision-Maui/assets/2202306/cde6e832-36b4-4789-9f8f-2398fb9f28c2
10 |
11 |
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/Platforms/Windows/App.xaml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Capture.Vision.Maui/AppBuilderExtensions.cs:
--------------------------------------------------------------------------------
1 | namespace Capture.Vision.Maui
2 | {
3 | public static class AppBuilderExtensions
4 | {
5 | public static MauiAppBuilder UseNativeCameraView(this MauiAppBuilder builder)
6 | {
7 | builder.ConfigureMauiHandlers(h =>
8 | {
9 | h.AddHandler(typeof(CameraView), typeof(CameraViewHandler));
10 | });
11 | return builder;
12 | }
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/Platforms/Tizen/Main.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Maui;
2 | using Microsoft.Maui.Hosting;
3 | using System;
4 |
5 | namespace Capture.Vision.Maui.Example
6 | {
7 | internal class Program : MauiApplication
8 | {
9 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
10 |
11 | static void Main(string[] args)
12 | {
13 | var app = new Program();
14 | app.Run(args);
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/Platforms/Android/MainApplication.cs:
--------------------------------------------------------------------------------
1 | using Android.App;
2 | using Android.Runtime;
3 |
4 | namespace Capture.Vision.Maui.Example
5 | {
6 | [Application]
7 | public class MainApplication : MauiApplication
8 | {
9 | public MainApplication(IntPtr handle, JniHandleOwnership ownership)
10 | : base(handle, ownership)
11 | {
12 | }
13 |
14 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
15 | }
16 | }
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/AppShell.xaml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/Platforms/Android/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/Platforms/MacCatalyst/Program.cs:
--------------------------------------------------------------------------------
1 | using ObjCRuntime;
2 | using UIKit;
3 |
4 | namespace Capture.Vision.Maui.Example
5 | {
6 | public class Program
7 | {
8 | // This is the main entry point of the application.
9 | static void Main(string[] args)
10 | {
11 | // if you want to use a different Application Delegate class from "AppDelegate"
12 | // you can specify it here.
13 | UIApplication.Main(args, null, typeof(AppDelegate));
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/MainPage.xaml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/Resources/Raw/AboutAssets.txt:
--------------------------------------------------------------------------------
1 | Any raw assets you want to be deployed with your application can be placed in
2 | this directory (and child directories). Deployment of the asset to your application
3 | is automatically handled by the following `MauiAsset` Build Action within your `.csproj`.
4 |
5 |
6 |
7 | These files will be deployed with you package and will be accessible using Essentials:
8 |
9 | async Task LoadMauiAsset()
10 | {
11 | using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt");
12 | using var reader = new StreamReader(stream);
13 |
14 | var contents = reader.ReadToEnd();
15 | }
16 |
--------------------------------------------------------------------------------
/Capture.Vision.Maui/CameraInfo.cs:
--------------------------------------------------------------------------------
1 | namespace Capture.Vision.Maui
2 | {
3 | public class CameraInfo
4 | {
5 | public enum Position
6 | {
7 | Back,
8 | Front,
9 | Unknown
10 | }
11 |
12 | public enum Status
13 | {
14 | Available,
15 | Unavailable
16 | }
17 |
18 | public string Name { get; internal set; }
19 | public string DeviceId { get; internal set; }
20 | public Position Pos { get; internal set; }
21 |
22 | public List AvailableResolutions { get; internal set; }
23 | public override string ToString()
24 | {
25 | return Name;
26 | }
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/App.xaml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/MauiProgram.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Logging;
2 | using SkiaSharp.Views.Maui.Controls.Hosting;
3 |
4 | namespace Capture.Vision.Maui.Example
5 | {
6 | public static class MauiProgram
7 | {
8 | public static MauiApp CreateMauiApp()
9 | {
10 | var builder = MauiApp.CreateBuilder();
11 | builder.UseSkiaSharp().UseNativeCameraView()
12 | .UseMauiApp()
13 | .ConfigureFonts(fonts =>
14 | {
15 | fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
16 | fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
17 | });
18 |
19 | #if DEBUG
20 | builder.Logging.AddDebug();
21 | #endif
22 |
23 | return builder.Build();
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/Platforms/Windows/app.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 | true/PM
12 | PerMonitorV2, PerMonitor
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/Platforms/Tizen/tizen-manifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | maui-appicon-placeholder
7 |
8 |
9 |
10 |
11 | http://tizen.org/privilege/internet
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/Platforms/iOS/Program.cs:
--------------------------------------------------------------------------------
1 | using ObjCRuntime;
2 | using UIKit;
3 | using Dynamsoft;
4 |
5 | namespace Capture.Vision.Maui.Example
6 | {
7 | public class Program
8 | {
9 | // This is the main entry point of the application.
10 | static void Main(string[] args)
11 | {
12 | DocumentScanner.InitLicense("DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==");
13 | BarcodeQRCodeReader.InitLicense("DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==");
14 | MrzScanner.InitLicense("DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==");
15 |
16 | UIApplication.Main(args, null, typeof(AppDelegate));
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/Capture.Vision.Maui.Example.csproj.user:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | False
5 | net7.0-windows10.0.19041.0
6 | Windows Machine
7 | PhysicalDevice
8 |
9 |
10 | ProjectDebugger
11 |
12 |
13 | ProjectDebugger
14 |
15 |
16 |
17 | Designer
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/Platforms/MacCatalyst/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | UIDeviceFamily
6 |
7 | 1
8 | 2
9 |
10 | UIRequiredDeviceCapabilities
11 |
12 | arm64
13 |
14 | UISupportedInterfaceOrientations
15 |
16 | UIInterfaceOrientationPortrait
17 | UIInterfaceOrientationLandscapeLeft
18 | UIInterfaceOrientationLandscapeRight
19 |
20 | UISupportedInterfaceOrientations~ipad
21 |
22 | UIInterfaceOrientationPortrait
23 | UIInterfaceOrientationPortraitUpsideDown
24 | UIInterfaceOrientationLandscapeLeft
25 | UIInterfaceOrientationLandscapeRight
26 |
27 | XSAppIconAssets
28 | Assets.xcassets/appicon.appiconset
29 |
30 |
31 |
--------------------------------------------------------------------------------
/Capture.Vision.Maui/Capture.Vision.Maui.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.8.34408.163
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Capture.Vision.Maui", "Capture.Vision.Maui.csproj", "{CECCA9D0-A805-4276-973A-F68F786111EA}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {CECCA9D0-A805-4276-973A-F68F786111EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {CECCA9D0-A805-4276-973A-F68F786111EA}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {CECCA9D0-A805-4276-973A-F68F786111EA}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {CECCA9D0-A805-4276-973A-F68F786111EA}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {C4156717-7AEF-437B-AE86-7114F09BEDE8}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/Platforms/Android/MainActivity.cs:
--------------------------------------------------------------------------------
1 | using Android.App;
2 | using Android.Content.PM;
3 | using Android.OS;
4 | using Dynamsoft;
5 |
6 | namespace Capture.Vision.Maui.Example
7 | {
8 | [Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
9 | public class MainActivity : MauiAppCompatActivity
10 | {
11 | protected override void OnCreate(Bundle savedInstanceState)
12 | {
13 | base.OnCreate(savedInstanceState);
14 |
15 | // Your platform-specific code here
16 | DocumentScanner.InitLicense("DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==");
17 | BarcodeQRCodeReader.InitLicense("DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==");
18 | MrzScanner.InitLicense("DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==", this);
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/Platforms/iOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | LSRequiresIPhoneOS
6 |
7 | UIDeviceFamily
8 |
9 | 1
10 | 2
11 |
12 | UIRequiredDeviceCapabilities
13 |
14 | arm64
15 |
16 | UISupportedInterfaceOrientations
17 |
18 | UIInterfaceOrientationPortrait
19 | UIInterfaceOrientationLandscapeLeft
20 | UIInterfaceOrientationLandscapeRight
21 |
22 | UISupportedInterfaceOrientations~ipad
23 |
24 | UIInterfaceOrientationPortrait
25 | UIInterfaceOrientationPortraitUpsideDown
26 | UIInterfaceOrientationLandscapeLeft
27 | UIInterfaceOrientationLandscapeRight
28 |
29 | XSAppIconAssets
30 | Assets.xcassets/appicon.appiconset
31 | NSCameraUsageDescription
32 | Use camera
33 | NSMicrophoneUsageDescription
34 | Use microphone
35 |
36 |
37 |
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/MainPage.xaml.cs:
--------------------------------------------------------------------------------
1 | using Dynamsoft;
2 |
3 | namespace Capture.Vision.Maui.Example
4 | {
5 | public partial class MainPage : ContentPage
6 | {
7 | public MainPage()
8 | {
9 | InitializeComponent();
10 | //InitService();
11 | }
12 |
13 | // private async void InitService()
14 | // {
15 | // await Task.Run(() =>
16 | // {
17 | // BarcodeQRCodeReader.InitLicense("DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==");
18 | //#if WINDOWS
19 | // DocumentScanner.InitLicense("DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==");
20 | // MrzScanner.InitLicense("DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==");
21 | //#elif ANDROID
22 | // // Android-specific code
23 | //#elif IOS
24 | // // iOS-specific code
25 | //#endif
26 | // return Task.CompletedTask;
27 | // });
28 | // }
29 |
30 | async void OnTakeVideoButtonClicked(object sender, EventArgs e)
31 | {
32 | await Navigation.PushAsync(new CameraPage());
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/Platforms/Windows/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using Dynamsoft;
2 | using Microsoft.UI.Xaml;
3 |
4 | // To learn more about WinUI, the WinUI project structure,
5 | // and more about our project templates, see: http://aka.ms/winui-project-info.
6 |
7 | namespace Capture.Vision.Maui.Example.WinUI
8 | {
9 | ///
10 | /// Provides application-specific behavior to supplement the default Application class.
11 | ///
12 | public partial class App : MauiWinUIApplication
13 | {
14 | ///
15 | /// Initializes the singleton application object. This is the first line of authored code
16 | /// executed, and as such is the logical equivalent of main() or WinMain().
17 | ///
18 | public App()
19 | {
20 | this.InitializeComponent();
21 | BarcodeQRCodeReader.InitLicense("DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==");
22 | DocumentScanner.InitLicense("DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==");
23 | MrzScanner.InitLicense("DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==");
24 | }
25 |
26 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
27 | }
28 | }
--------------------------------------------------------------------------------
/Capture.Vision.Maui/DocumenResult.cs:
--------------------------------------------------------------------------------
1 | using static Dynamsoft.DocumentScanner;
2 |
3 | namespace Capture.Vision.Maui
4 | {
5 | public class DocumentResult
6 | {
7 | public int Confidence { get; set; }
8 |
9 | public int[] Points { get; set; }
10 |
11 | public int Width;
12 |
13 | public int Height;
14 |
15 | public int Stride;
16 |
17 | public ImagePixelFormat Format;
18 |
19 | public byte[] Data { get; set; }
20 |
21 | public byte[] Binary2Grayscale()
22 | {
23 | byte[] array = new byte[Width * Height];
24 | int num = 0;
25 | int num2 = Stride * 8 - Width;
26 | int num3 = 0;
27 | int num4 = 1;
28 | byte[] data = Data;
29 | foreach (byte b in data)
30 | {
31 | int num5 = 7;
32 | while (num5 >= 0)
33 | {
34 | int num6 = (b & (1 << num5)) >> num5;
35 | if (num3 < Stride * 8 * num4 - num2)
36 | {
37 | if (num6 == 1)
38 | {
39 | array[num] = byte.MaxValue;
40 | }
41 | else
42 | {
43 | array[num] = 0;
44 | }
45 |
46 | num++;
47 | }
48 |
49 | num5--;
50 | num3++;
51 | }
52 |
53 | if (num3 == Stride * 8 * num4)
54 | {
55 | num4++;
56 | }
57 | }
58 |
59 | return array;
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/Resources/Splash/splash.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/Resources/AppIcon/appiconfg.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/Capture.Vision.Maui.Example.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.5.002.0
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Capture.Vision.Maui.Example", "Capture.Vision.Maui.Example.csproj", "{CD0F5B14-7569-4F67-BF1A-F1FA9903B66A}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Capture.Vision.Maui", "..\Capture.Vision.Maui\Capture.Vision.Maui.csproj", "{F521F9FE-FF37-41D4-A476-8B63D4AF9167}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {CD0F5B14-7569-4F67-BF1A-F1FA9903B66A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {CD0F5B14-7569-4F67-BF1A-F1FA9903B66A}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {CD0F5B14-7569-4F67-BF1A-F1FA9903B66A}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
19 | {CD0F5B14-7569-4F67-BF1A-F1FA9903B66A}.Release|Any CPU.ActiveCfg = Release|Any CPU
20 | {CD0F5B14-7569-4F67-BF1A-F1FA9903B66A}.Release|Any CPU.Build.0 = Release|Any CPU
21 | {F521F9FE-FF37-41D4-A476-8B63D4AF9167}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
22 | {F521F9FE-FF37-41D4-A476-8B63D4AF9167}.Debug|Any CPU.Build.0 = Debug|Any CPU
23 | {F521F9FE-FF37-41D4-A476-8B63D4AF9167}.Release|Any CPU.ActiveCfg = Release|Any CPU
24 | {F521F9FE-FF37-41D4-A476-8B63D4AF9167}.Release|Any CPU.Build.0 = Release|Any CPU
25 | EndGlobalSection
26 | GlobalSection(SolutionProperties) = preSolution
27 | HideSolutionNode = FALSE
28 | EndGlobalSection
29 | GlobalSection(ExtensibilityGlobals) = postSolution
30 | SolutionGuid = {6DBB7DB9-F5C5-4859-9DCE-A0E290680D8D}
31 | EndGlobalSection
32 | EndGlobal
33 |
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/Platforms/Windows/Package.appxmanifest:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | $placeholder$
15 | User Name
16 | $placeholder$.png
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/CameraPage.xaml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
14 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/Resources/Styles/Colors.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 | #512BD4
8 | #DFD8F7
9 | #2B0B98
10 | White
11 | Black
12 | #E1E1E1
13 | #C8C8C8
14 | #ACACAC
15 | #919191
16 | #6E6E6E
17 | #404040
18 | #212121
19 | #141414
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | #F7B548
35 | #FFD590
36 | #FFE5B9
37 | #28C2D1
38 | #7BDDEF
39 | #C3F2F4
40 | #3E8EED
41 | #72ACF1
42 | #A7CBF6
43 |
44 |
--------------------------------------------------------------------------------
/Capture.Vision.Maui/CameraViewHandler.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Maui.Handlers;
2 | using static Capture.Vision.Maui.CameraInfo;
3 | #if IOS
4 | using PlatformView = Capture.Vision.Maui.Platforms.iOS.NativeCameraView;
5 | #elif ANDROID
6 | using PlatformView = Capture.Vision.Maui.Platforms.Android.NativeCameraView;
7 | #elif WINDOWS
8 | using PlatformView = Capture.Vision.Maui.Platforms.Windows.NativeCameraView;
9 | #else
10 | using PlatformView = System.Object;
11 | #endif
12 |
13 | namespace Capture.Vision.Maui
14 | {
15 |
16 |
17 |
18 | internal partial class CameraViewHandler : ViewHandler
19 | {
20 | public static IPropertyMapper PropertyMapper = new PropertyMapper(ViewMapper)
21 | {
22 | };
23 | public static CommandMapper CommandMapper = new(ViewCommandMapper)
24 | {
25 | };
26 | public CameraViewHandler() : base(PropertyMapper, CommandMapper)
27 | {
28 | }
29 |
30 | #if ANDROID
31 | protected override PlatformView CreatePlatformView() => new(Context, VirtualView);
32 | #elif IOS || WINDOWS
33 | protected override PlatformView CreatePlatformView() => new(VirtualView);
34 | #else
35 | protected override PlatformView CreatePlatformView() => new();
36 | #endif
37 | protected override void ConnectHandler(PlatformView platformView)
38 | {
39 | base.ConnectHandler(platformView);
40 | }
41 |
42 | protected override void DisconnectHandler(PlatformView platformView)
43 | {
44 | #if WINDOWS || IOS || ANDROID
45 | platformView.DisposeControl();
46 | #endif
47 | base.DisconnectHandler(platformView);
48 | }
49 |
50 | public Task StartCameraAsync()
51 | {
52 | if (PlatformView != null)
53 | {
54 | #if WINDOWS || ANDROID || IOS
55 | return PlatformView.StartCameraAsync();
56 | #endif
57 | }
58 | return Task.Run(() => { return Status.Unavailable; });
59 | }
60 |
61 | public Task StopCameraAsync()
62 | {
63 | if (PlatformView != null)
64 | {
65 | #if WINDOWS
66 | return PlatformView.StopCameraAsync();
67 | #elif ANDROID || IOS
68 | var task = new Task(() => { return PlatformView.StopCamera(); });
69 | task.Start();
70 | return task;
71 | #endif
72 | }
73 | return Task.Run(() => { return Status.Unavailable; });
74 | }
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/Capture.Vision.Maui/Capture.Vision.Maui.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 2.0.1
5 | net7.0;net7.0-android;net7.0-ios
6 |
7 | $(TargetFrameworks);net7.0-windows10.0.19041.0
8 | true
9 | true
10 | enable
11 | True
12 |
14 | 14.2
15 |
17 | 14.0
18 |
20 | 28.0
21 |
23 | 10.0.17763.0
24 |
26 | 10.0.17763.0
27 | The plugin aims to assist developers in creating .NET MAUI applications
28 | featuring a custom camera view. It utilizes Dynamsoft Vision SDKs for barcode, MRZ
29 | (Machine Readable Zone), and document detection.
30 | https://github.com/yushulx/Capture-Vision-Maui
31 | https://github.com/yushulx/Capture-Vision-Maui
32 |
33 | barcode;qrcode;Aztec;QR;Datamatrix;PDF417;Code39;Code93;Code128;Codabar;Interleaved;EAN-8;EAN-13;UPC-A;UPC-E;GS1Databar;PatchCode;Maxicode;COMPOSITE;PostalCode;document;MRZ;passport;ID;visa
34 |
35 | - Update the dependencies to the latest version
36 |
37 | MIT
38 | true
39 | README.md
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
68 |
69 |
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/Capture.Vision.Maui.Example.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net7.0-android;net7.0-ios;net7.0-maccatalyst
5 | $(TargetFrameworks);net7.0-windows10.0.19041.0
6 |
7 |
8 | Exe
9 | Capture.Vision.Maui.Example
10 | true
11 | true
12 | enable
13 |
14 |
15 | Capture.Vision.Maui.Example
16 |
17 |
18 | com.companyname.capture.vision.maui.example
19 | ab0922f9-94a0-4f7b-bac5-2b7a8772b6a7
20 |
21 |
22 | 1.0
23 | 1
24 |
25 | 11.0
26 | 13.1
27 | 21.0
28 | 10.0.17763.0
29 | 10.0.17763.0
30 | 6.5
31 |
32 |
33 |
34 | manual
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | MSBuild:Compile
71 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/Capture.Vision.Maui/MrzResult.cs:
--------------------------------------------------------------------------------
1 | namespace Capture.Vision.Maui
2 | {
3 | public class Line
4 | {
5 | public int Confidence { get; set; }
6 |
7 | public string Text { get; set; }
8 |
9 | public int[] Points { get; set; }
10 | }
11 |
12 | public class MrzResult
13 | {
14 | public string Type { get; set; } = "N/A";
15 | public string Nationality { get; set; } = "N/A";
16 | public string Surname { get; set; } = "N/A";
17 | public string GivenName { get; set; } = "N/A";
18 | public string PassportNumber { get; set; } = "N/A";
19 | public string IssuingCountry { get; set; } = "N/A";
20 | public string BirthDate { get; set; } = "N/A";
21 | public string Gender { get; set; } = "N/A";
22 | public string Expiration { get; set; } = "N/A";
23 | public string Lines { get; set; } = "N/A";
24 |
25 | public Line[] RawData { get; set; }
26 |
27 | // Constructor
28 | public MrzResult(
29 | string type = "N/A",
30 | string nationality = "N/A",
31 | string surname = "N/A",
32 | string givenName = "N/A",
33 | string passportNumber = "N/A",
34 | string issuingCountry = "N/A",
35 | string birthDate = "N/A",
36 | string gender = "N/A",
37 | string expiration = "N/A",
38 | string lines = "N/A")
39 | {
40 | Type = type;
41 | Nationality = nationality;
42 | Surname = surname;
43 | GivenName = givenName;
44 | PassportNumber = passportNumber;
45 | IssuingCountry = issuingCountry;
46 | BirthDate = birthDate;
47 | Gender = gender;
48 | Expiration = expiration;
49 | Lines = lines;
50 | }
51 |
52 | // ToString Method
53 | public override string ToString()
54 | {
55 | if (string.IsNullOrEmpty(Type)) return "No results";
56 |
57 | return $"Type: {Type}\n\n" +
58 | $"Nationality: {Nationality}\n\n" +
59 | $"Surname: {Surname}\n\n" +
60 | $"Given name: {GivenName}\n\n" +
61 | $"Passport Number: {PassportNumber}\n\n" +
62 | $"Issue Country: {IssuingCountry}\n\n" +
63 | $"Date of birth: {BirthDate}\n\n" +
64 | $"Gender: {Gender}\n\n" +
65 | $"Expiration: {Expiration}\n\n" + $"Lines: {Lines}\n\n";
66 | }
67 |
68 | // ToJson Method
69 | public Dictionary ToJson()
70 | {
71 | return new Dictionary
72 | {
73 | { "type", Type ?? "" },
74 | { "nationality", Nationality ?? "" },
75 | { "surname", Surname ?? "" },
76 | { "givenName", GivenName ?? "" },
77 | { "passportNumber", PassportNumber ?? "" },
78 | { "issuingCountry", IssuingCountry ?? "" },
79 | { "birthDate", BirthDate ?? "" },
80 | { "gender", Gender ?? "" },
81 | { "expiration", Expiration ?? "" },
82 | { "lines", Lines }
83 | };
84 | }
85 |
86 | // FromJson Factory Method
87 | public static MrzResult FromJson(Dictionary json)
88 | {
89 | return new MrzResult(
90 | json.ContainsKey("type") ? json["type"].ToString() : "N/A",
91 | json.ContainsKey("nationality") ? json["nationality"].ToString() : "N/A",
92 | json.ContainsKey("surname") ? json["surname"].ToString() : "N/A",
93 | json.ContainsKey("givenName") ? json["givenName"].ToString() : "N/A",
94 | json.ContainsKey("passportNumber") ? json["passportNumber"].ToString() : "N/A",
95 | json.ContainsKey("issuingCountry") ? json["issuingCountry"].ToString() : "N/A",
96 | json.ContainsKey("birthDate") ? json["birthDate"].ToString() : "N/A",
97 | json.ContainsKey("gender") ? json["gender"].ToString() : "N/A",
98 | json.ContainsKey("expiration") ? json["expiration"].ToString() : "N/A",
99 | json.ContainsKey("lines") ? json["lines"].ToString() : "N/A"
100 | );
101 | }
102 |
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/Capture.Vision.Maui/CameraView.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.ObjectModel;
2 | using static Capture.Vision.Maui.CameraInfo;
3 |
4 | namespace Capture.Vision.Maui
5 | {
6 | public class ResultReadyEventArgs : EventArgs
7 | {
8 | public ResultReadyEventArgs(object result, int previewWidth, int previewHeight)
9 | {
10 | Result = result;
11 | PreviewWidth = previewWidth;
12 | PreviewHeight = previewHeight;
13 | }
14 |
15 | public object Result { get; private set; }
16 | public int PreviewWidth { get; private set; }
17 | public int PreviewHeight { get; private set; }
18 |
19 | }
20 |
21 | public class FrameReadyEventArgs : EventArgs
22 | {
23 | public enum PixelFormat
24 | {
25 | GRAYSCALE,
26 | RGB888,
27 | BGR888,
28 | RGBA8888,
29 | BGRA8888,
30 | }
31 | public FrameReadyEventArgs(byte[] buffer, int width, int height, int stride, PixelFormat pixelFormat)
32 | {
33 | Buffer = buffer;
34 | Width = width;
35 | Height = height;
36 | Stride = stride;
37 | Format = pixelFormat;
38 | }
39 |
40 | public byte[] Buffer { get; private set; }
41 | public int Width { get; private set; }
42 | public int Height { get; private set; }
43 | public int Stride { get; private set; }
44 | public PixelFormat Format { get; private set; }
45 | }
46 |
47 | public class CameraView : View
48 | {
49 | public static readonly BindableProperty CamerasProperty = BindableProperty.Create(nameof(Cameras), typeof(ObservableCollection), typeof(CameraView), new ObservableCollection());
50 | public static readonly BindableProperty CameraProperty = BindableProperty.Create(nameof(Camera), typeof(CameraInfo), typeof(CameraView), null);
51 | public static readonly BindableProperty EnableBarcodeProperty = BindableProperty.Create(nameof(EnableBarcode), typeof(bool), typeof(CameraView), false);
52 | public static readonly BindableProperty EnableDocumentDetectProperty = BindableProperty.Create(nameof(EnableDocumentDetectProperty), typeof(bool), typeof(CameraView), false);
53 | public static readonly BindableProperty EnableDocumentRectifyProperty = BindableProperty.Create(nameof(EnableDocumentRectifyProperty), typeof(bool), typeof(CameraView), false);
54 | public static readonly BindableProperty EnableMrzProperty = BindableProperty.Create(nameof(EnableMrzProperty), typeof(bool), typeof(CameraView), false);
55 | public static readonly BindableProperty ShowCameraViewProperty = BindableProperty.Create(nameof(ShowCameraView), typeof(bool), typeof(CameraView), false, propertyChanged: ShowCameraViewChanged);
56 | public event EventHandler ResultReady;
57 | public event EventHandler FrameReady;
58 | public string BarcodeParameters { get; set; } = string.Empty;
59 |
60 | public ObservableCollection Cameras
61 | {
62 | get { return (ObservableCollection)GetValue(CamerasProperty); }
63 | set { SetValue(CamerasProperty, value); }
64 | }
65 |
66 | public CameraInfo Camera
67 | {
68 | get { return (CameraInfo)GetValue(CameraProperty); }
69 | set { SetValue(CameraProperty, value); }
70 | }
71 |
72 | public bool EnableBarcode
73 | {
74 | get { return (bool)GetValue(EnableBarcodeProperty); }
75 | set { SetValue(EnableBarcodeProperty, value); }
76 | }
77 |
78 | public bool EnableDocumentDetect
79 | {
80 | get { return (bool)GetValue(EnableDocumentDetectProperty); }
81 | set { SetValue(EnableDocumentDetectProperty, value); }
82 | }
83 |
84 | public bool EnableDocumentRectify
85 | {
86 | get { return (bool)GetValue(EnableDocumentRectifyProperty); }
87 | set { SetValue(EnableDocumentRectifyProperty, value); }
88 | }
89 |
90 | public bool EnableMrz
91 | {
92 | get { return (bool)GetValue(EnableMrzProperty); }
93 | set { SetValue(EnableMrzProperty, value); }
94 | }
95 |
96 | public bool ShowCameraView
97 | {
98 | get { return (bool)GetValue(ShowCameraViewProperty); }
99 | set { SetValue(ShowCameraViewProperty, value); }
100 | }
101 |
102 | public void NotifyResultReady(object result, int previewWidth, int previewHeight)
103 | {
104 | if (ResultReady != null)
105 | {
106 | ResultReady(this, new ResultReadyEventArgs(result, previewWidth, previewHeight));
107 | }
108 | }
109 |
110 | public void NotifyFrameReady(byte[] buffer, int width, int height, int stride, FrameReadyEventArgs.PixelFormat format)
111 | {
112 | if (FrameReady != null)
113 | {
114 | FrameReady(this, new FrameReadyEventArgs(buffer, width, height, stride, format));
115 | }
116 | }
117 |
118 | private static async void ShowCameraViewChanged(BindableObject bindable, object oldValue, object newValue)
119 | {
120 | if (oldValue != newValue && bindable is CameraView control)
121 | {
122 | try
123 | {
124 | if ((bool)newValue)
125 | await control.StartCameraAsync();
126 | else
127 | await control.StopCameraAsync();
128 | }
129 | catch { }
130 | }
131 | }
132 |
133 | public async Task StartCameraAsync()
134 | {
135 | Status result = Status.Unavailable;
136 | if (Camera != null)
137 | {
138 | if (Handler != null && Handler is CameraViewHandler handler)
139 | {
140 | result = await handler.StartCameraAsync();
141 | }
142 | }
143 |
144 | return result;
145 | }
146 |
147 | public async Task StopCameraAsync()
148 | {
149 | Status result = Status.Unavailable;
150 | if (Handler != null && Handler is CameraViewHandler handler)
151 | {
152 | result = await handler.StopCameraAsync();
153 | }
154 | return result;
155 | }
156 |
157 | public void UpdateCameras()
158 | {
159 | Task.Run(() => {
160 |
161 | MainThread.BeginInvokeOnMainThread(() => {
162 | if (Cameras.Count > 0)
163 | {
164 | Camera = Cameras.First();
165 | ShowCameraView = true;
166 | OnPropertyChanged(nameof(ShowCameraView));
167 | }
168 | });
169 |
170 | });
171 | }
172 | public static async Task RequestPermissions()
173 | {
174 | var status = await Permissions.CheckStatusAsync();
175 | if (status != PermissionStatus.Granted)
176 | {
177 | status = await Permissions.RequestAsync();
178 | if (status != PermissionStatus.Granted) return false;
179 | }
180 | return true;
181 | }
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # .NET MAUI Camera View with Dynamsoft Vision SDKs
2 | This project helps developers create .NET MAUI applications featuring a custom camera view using [Dynamsoft Vision SDKs](https://www.dynamsoft.com/). These SDKs provide capabilities for barcode, MRZ (Machine Readable Zone), and document detection.
3 |
4 | ## Example
5 | [https://github.com/yushulx/Capture-Vision-Maui/tree/main/Capture.Vision.Maui.Example](https://github.com/yushulx/Capture-Vision-Maui/tree/main/Capture.Vision.Maui.Example)
6 |
7 | - Windows
8 |
9 | 
10 |
11 | - iOS
12 |
13 | 
14 |
15 | ## Demo Video: .NET MAUI QR Code Scanner
16 |
17 | - Windows
18 |
19 | [https://github.com/yushulx/Capture-Vision-Maui/assets/2202306/df6ce0d1-93b8-4e26-be6a-cfe82ba3d267](https://github.com/yushulx/Capture-Vision-Maui/assets/2202306/df6ce0d1-93b8-4e26-be6a-cfe82ba3d267)
20 |
21 | - Android
22 |
23 | [https://github.com/yushulx/Capture-Vision-Maui/assets/2202306/73551440-6720-4912-8605-cee9882bbee2](https://github.com/yushulx/Capture-Vision-Maui/assets/2202306/73551440-6720-4912-8605-cee9882bbee2)
24 |
25 |
26 |
27 | ## Supported Platforms
28 | - Android
29 | - iOS
30 | - Windows
31 |
32 | ## Features
33 | - Scan 1D barcodes, QR codes, PDF417, DataMatrix, and other barcode formats.
34 | - Recognize Machine Readable Zones (MRZ) from camera frames.
35 | - Detect document edges in real-time.
36 |
37 | ## Getting Started
38 | 1. Enable the camera view in `MauiProgram.cs`:
39 |
40 | ```csharp
41 | builder.UseNativeCameraView()
42 | ```
43 |
44 | 2. Request a [free trial license](https://www.dynamsoft.com/customer/license/trialLicense/?product=dcv&package=cross-platform) and replace `LICENSE-KEY` with your own license key in the platform-specific code.
45 |
46 | **App.xaml.cs for Windows:**
47 |
48 | ```csharp
49 | using Dynamsoft;
50 | using Microsoft.UI.Xaml;
51 | namespace Capture.Vision.Maui.Example.WinUI
52 | {
53 | public partial class App : MauiWinUIApplication
54 | {
55 | public App()
56 | {
57 | this.InitializeComponent();
58 | BarcodeQRCodeReader.InitLicense("LICENSE-KEY");
59 | DocumentScanner.InitLicense("LICENSE-KEY");
60 | MrzScanner.InitLicense("LICENSE-KEY");
61 | }
62 | }
63 | }
64 | ```
65 |
66 | **MainActivity.cs for Android:**
67 |
68 | ```csharp
69 | using Android.App;
70 | using Android.Content.PM;
71 | using Android.OS;
72 | using Dynamsoft;
73 |
74 | namespace Capture.Vision.Maui.Example
75 | {
76 | public class MainActivity : MauiAppCompatActivity
77 | {
78 | protected override void OnCreate(Bundle savedInstanceState)
79 | {
80 | base.OnCreate(savedInstanceState);
81 |
82 | // Your platform-specific code here
83 | BarcodeQRCodeReader.InitLicense("LICENSE-KEY");
84 | DocumentScanner.InitLicense("LICENSE-KEY");
85 | MrzScanner.InitLicense("LICENSE-KEY");
86 | }
87 | }
88 | }
89 | ```
90 |
91 | **Program.cs for iOS:**
92 |
93 | ```csharp
94 | using ObjCRuntime;
95 | using UIKit;
96 | using Dynamsoft;
97 |
98 | static void Main(string[] args)
99 | {
100 | DocumentScanner.InitLicense("LICENSE-KEY");
101 | BarcodeQRCodeReader.InitLicense("LICENSE-KEY");
102 | MrzScanner.InitLicense("LICENSE-KEY");
103 |
104 | UIApplication.Main(args, null, typeof(AppDelegate));
105 | }
106 | ```
107 |
108 | 3. Create a content page to add the camera view:
109 |
110 | ```xml
111 |
112 |
118 |
119 |
120 |
124 |
128 |
129 |
130 |
131 | ```
132 |
133 | Set `EnableBarcode`, `EnableDocumentDetect`, and `EnableMrz` to activate barcode, document, and MRZ (Machine Readable Zone) detection, respectively. Use the `cameraView_ResultReady` event to retrieve the results.
134 |
135 | ```csharp
136 | private void cameraView_ResultReady(object sender, ResultReadyEventArgs e)
137 | {
138 | if (e.Result is BarcodeResult[])
139 | {}
140 | else if (e.Result is DocumentResult)
141 | {}
142 | else if (e.Result is MrzResult)
143 | {}
144 | }
145 | ```
146 |
147 | ## Custom Image Processing
148 | In the `cameraView_FrameReady` event, access the camera frame for custom image processing:
149 |
150 | ```csharp
151 | private void cameraView_FrameReady(object sender, FrameReadyEventArgs e)
152 | {
153 | // process image
154 | }
155 | ```
156 |
157 | The `FrameReadyEventArgs` provide the image buffer, width, height, stride and format.
158 |
159 | ```csharp
160 | public class FrameReadyEventArgs : EventArgs
161 | {
162 | public enum PixelFormat
163 | {
164 | GRAYSCALE,
165 | RGB888,
166 | BGR888,
167 | RGBA8888,
168 | BGRA8888,
169 | }
170 | public FrameReadyEventArgs(byte[] buffer, int width, int height, int stride, PixelFormat pixelFormat)
171 | {
172 | Buffer = buffer;
173 | Width = width;
174 | Height = height;
175 | Stride = stride;
176 | Format = pixelFormat;
177 | }
178 |
179 | public byte[] Buffer { get; private set; }
180 | public int Width { get; private set; }
181 | public int Height { get; private set; }
182 | public int Stride { get; private set; }
183 | public PixelFormat Format { get; private set; }
184 | }
185 | ```
186 |
187 | Currently, frames captured from Windows and Android devices are in the `GRAYSCALE` format, whereas frames from iOS devices use the `BGRA8888` format.
188 |
189 |
190 | ## Barcode Parameters Configuration
191 | Customize barcode detection parameters to fit specific requirements, such as supported barcode formats and expected counts:
192 |
193 | ```csharp
194 | public CameraPage()
195 | {
196 | InitializeComponent();
197 | ...
198 | // cameraView.BarcodeParameters = "{\"Version\":\"3.0\", \"ImageParameter\":{\"Name\":\"IP1\", \"BarcodeFormatIds\":[\"BF_QR_CODE\", \"BF_ONED\"], \"ExpectedBarcodesCount\":20}}";
199 | }
200 | ```
201 |
202 | ## References
203 | - Camera: [https://github.com/hjam40/Camera.MAUI](https://github.com/hjam40/Camera.MAUI)
204 | - Barcode: [https://github.com/yushulx/dotnet-barcode-qr-code-sdk](https://github.com/yushulx/dotnet-barcode-qr-code-sdk)
205 | - Capture Vision: [https://github.com/yushulx/Capture-Vision](https://github.com/yushulx/Capture-Vision)
206 |
207 |
208 | ## Building NuGet Package from Source Code
209 |
210 | ```bash
211 | cd Capture.Vision.Maui
212 | dotnet build --configuration Release
213 | ```
214 |
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/Resources/Images/dotnet_bot.svg:
--------------------------------------------------------------------------------
1 |
94 |
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/CameraPage.xaml.cs:
--------------------------------------------------------------------------------
1 | using SkiaSharp;
2 | using SkiaSharp.Views.Maui;
3 | using System.Runtime.InteropServices;
4 | using static Capture.Vision.Maui.FrameReadyEventArgs;
5 |
6 | namespace Capture.Vision.Maui.Example;
7 |
8 | public class BarcodeQrData
9 | {
10 | public BarcodeResult Reference { get; set; }
11 | public SKPoint[] Points;
12 | }
13 |
14 | public class DocumentData
15 | {
16 | public DocumentResult Reference { get; set; }
17 | public SKPoint[] Points;
18 | }
19 |
20 | public class MrzData
21 | {
22 | public MrzResult Reference { get; set; }
23 | public SKPoint[][] Points;
24 | }
25 |
26 | public partial class CameraPage : ContentPage
27 | {
28 | BarcodeQrData[] barcodeData = null;
29 | DocumentData documentData = null;
30 | MrzData mrzData = null;
31 | private int imageWidth;
32 | private int imageHeight;
33 | bool saveImage = true;
34 | private static object _lockObject = new object();
35 | DisplayOrientation orientation;
36 | DisplayRotation rotation;
37 | DisplayInfo mainDisplayInfo;
38 | double density, scale, widthScale, heightScale, scaledWidth, scaledHeight, width, height;
39 | bool isFirstTime = true;
40 | public CameraPage()
41 | {
42 | InitializeComponent();
43 | this.Disappearing += OnDisappearing;
44 |
45 | mainDisplayInfo = DeviceDisplay.MainDisplayInfo;
46 | orientation = mainDisplayInfo.Orientation;
47 | rotation = mainDisplayInfo.Rotation;
48 | density = mainDisplayInfo.Density;
49 | //cameraView.BarcodeParameters = "{\"Version\":\"3.0\", \"ImageParameter\":{\"Name\":\"IP1\", \"BarcodeFormatIds\":[\"BF_QR_CODE\", \"BF_ONED\"], \"ExpectedBarcodesCount\":20}}";
50 | checkBoxBarcode.CheckedChanged += OnBarcodeCheckedChanged;
51 | checkBoxDocument.CheckedChanged += OnDocumentCheckedChanged;
52 | checkBoxMrz.CheckedChanged += OnMrzCheckedChanged;
53 | }
54 |
55 | private void OnDisappearing(object sender, EventArgs e)
56 | {
57 | cameraView.ShowCameraView = false;
58 | }
59 |
60 | private void OnBarcodeCheckedChanged(object sender, CheckedChangedEventArgs e)
61 | {
62 | cameraView.EnableBarcode = e.Value;
63 | if (!cameraView.EnableBarcode)
64 | {
65 | barcodeData = null;
66 | }
67 | }
68 |
69 | private void OnDocumentCheckedChanged(object sender, CheckedChangedEventArgs e)
70 | {
71 | cameraView.EnableDocumentDetect = e.Value;
72 | if (!cameraView.EnableDocumentDetect)
73 | {
74 | documentData = null;
75 | }
76 | }
77 |
78 | private void OnMrzCheckedChanged(object sender, CheckedChangedEventArgs e)
79 | {
80 | cameraView.EnableMrz = e.Value;
81 | if (!cameraView.EnableMrz)
82 | {
83 | mrzData = null;
84 | }
85 | }
86 |
87 | private void cameraView_FrameReady(object sender, FrameReadyEventArgs e)
88 | {
89 | MainThread.BeginInvokeOnMainThread(() =>
90 | {
91 | canvasView.InvalidateSurface();
92 | });
93 | // process image
94 | if (saveImage)
95 | {
96 | saveImage = false;
97 | byte[] buffer = (byte[])e.Buffer;
98 | int width = e.Width;
99 | int height = e.Height;
100 | int stride = e.Stride;
101 | PixelFormat format = e.Format;
102 |
103 | SKImageInfo info = new SKImageInfo(width, height, SKColorType.Gray8, SKAlphaType.Premul);
104 |
105 | // Create the SKBitmap
106 | SKBitmap bitmap = new SKBitmap(info);
107 | bitmap.SetPixels((Marshal.UnsafeAddrOfPinnedArrayElement(buffer, 0)));
108 |
109 | // Save the data to a file
110 | using SKImage image = SKImage.FromBitmap(bitmap);
111 | using SKData data = image.Encode(SKEncodedImageFormat.Jpeg, 100);
112 | var appDataDirectory = FileSystem.AppDataDirectory;
113 |
114 | // Combine the app's data directory path with your file name
115 | var filePath = Path.Combine(appDataDirectory, "yourfile.jpg");
116 |
117 | using FileStream stream = File.OpenWrite(filePath);
118 | data.SaveTo(stream);
119 | }
120 | }
121 |
122 | private void cameraView_ResultReady(object sender, ResultReadyEventArgs e)
123 | {
124 | lock (_lockObject)
125 | {
126 | imageWidth = e.PreviewWidth;
127 | imageHeight = e.PreviewHeight;
128 | if (orientation == DisplayOrientation.Portrait)
129 | {
130 | widthScale = imageHeight / width;
131 | heightScale = imageWidth / height;
132 | scale = widthScale < heightScale ? widthScale : heightScale;
133 | scaledWidth = imageHeight / scale;
134 | scaledHeight = imageWidth / scale;
135 | }
136 | else
137 | {
138 | widthScale = imageWidth / width;
139 | heightScale = imageHeight / height;
140 | scale = widthScale < heightScale ? widthScale : heightScale;
141 | scaledWidth = imageWidth / scale;
142 | scaledHeight = imageHeight / scale;
143 | }
144 |
145 | if (e.Result is BarcodeResult[])
146 | {
147 | barcodeData = null;
148 | BarcodeResult[] barcodeResults = (BarcodeResult[])e.Result;
149 |
150 | if (barcodeResults != null && barcodeResults.Length > 0)
151 | {
152 | barcodeData = new BarcodeQrData[barcodeResults.Length];
153 |
154 | for (int index = 0; index < barcodeResults.Length; ++index)
155 | {
156 | barcodeData[index] = new BarcodeQrData()
157 | {
158 | Reference = barcodeResults[index]
159 | };
160 | int[] coordinates = barcodeResults[index].Points;
161 | if (coordinates != null && coordinates.Length == 8)
162 | {
163 | barcodeData[index].Points = new SKPoint[4];
164 |
165 | for (int i = 0; i < 4; ++i)
166 | {
167 | SKPoint p = new SKPoint();
168 | p.X = coordinates[i * 2];
169 | p.Y = coordinates[i * 2 + 1];
170 | barcodeData[index].Points[i] = p;
171 |
172 | if (orientation == DisplayOrientation.Portrait)
173 | {
174 | barcodeData[index].Points[i] = rotateCW90(barcodeData[index].Points[i], imageHeight);
175 | }
176 |
177 | barcodeData[index].Points[i].X = (float)(barcodeData[index].Points[i].X / scale);
178 | barcodeData[index].Points[i].Y = (float)(barcodeData[index].Points[i].Y / scale);
179 | }
180 | }
181 | }
182 | }
183 | }
184 | else if (e.Result is DocumentResult)
185 | {
186 | documentData = null;
187 | DocumentResult documentResult = (DocumentResult)e.Result;
188 |
189 | if (documentResult.Points != null)
190 | {
191 | documentData = new DocumentData()
192 | {
193 | Reference = documentResult
194 | };
195 | int[] coordinates = documentData.Reference.Points;
196 | if (coordinates != null && coordinates.Length == 8)
197 | {
198 | documentData.Points = new SKPoint[4];
199 |
200 | for (int i = 0; i < 4; ++i)
201 | {
202 | SKPoint p = new SKPoint();
203 | p.X = coordinates[i * 2];
204 | p.Y = coordinates[i * 2 + 1];
205 | documentData.Points[i] = p;
206 |
207 | if (orientation == DisplayOrientation.Portrait)
208 | {
209 | documentData.Points[i] = rotateCW90(documentData.Points[i], imageHeight);
210 | }
211 |
212 | documentData.Points[i].X = (float)(documentData.Points[i].X / scale);
213 | documentData.Points[i].Y = (float)(documentData.Points[i].Y / scale);
214 | }
215 | }
216 | }
217 | }
218 | else if (e.Result is MrzResult)
219 | {
220 | mrzData = null;
221 | MrzResult mrzResult = (MrzResult)e.Result;
222 |
223 | if (mrzResult.RawData != null)
224 | {
225 | mrzData = new MrzData()
226 | {
227 | Reference = mrzResult
228 | };
229 |
230 | Line[] rawData = mrzData.Reference.RawData;
231 | mrzData.Points = new SKPoint[rawData.Length][];
232 | for (int index = 0; index < rawData.Length; index++)
233 | {
234 | Line line = rawData[index];
235 | int[] coordinates = line.Points;
236 | mrzData.Points[index] = new SKPoint[4];
237 | for (int i = 0; i < 4; ++i)
238 | {
239 | SKPoint p = new SKPoint();
240 | p.X = coordinates[i * 2];
241 | p.Y = coordinates[i * 2 + 1];
242 | mrzData.Points[index][i] = p;
243 |
244 | if (orientation == DisplayOrientation.Portrait)
245 | {
246 | mrzData.Points[index][i] = rotateCW90(mrzData.Points[index][i], imageHeight);
247 | }
248 |
249 | mrzData.Points[index][i].X = (float)(mrzData.Points[index][i].X / scale);
250 | mrzData.Points[index][i].Y = (float)(mrzData.Points[index][i].Y / scale);
251 | }
252 | }
253 | }
254 | }
255 | }
256 |
257 | }
258 |
259 | public static SKPoint rotateCW90(SKPoint point, int width)
260 | {
261 | SKPoint rotatedPoint = new SKPoint();
262 | rotatedPoint.X = width - point.Y;
263 | rotatedPoint.Y = point.X;
264 | return rotatedPoint;
265 | }
266 |
267 | void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
268 | {
269 | if (isFirstTime)
270 | {
271 | isFirstTime = false;
272 | width = canvasView.Width;
273 | height = canvasView.Height;
274 |
275 | width *= density;
276 | height *= density;
277 | }
278 |
279 | SKImageInfo info = args.Info;
280 | SKSurface surface = args.Surface;
281 | SKCanvas canvas = surface.Canvas;
282 |
283 | canvas.Clear();
284 |
285 | lock (_lockObject)
286 | {
287 | if (barcodeData != null && cameraView.EnableBarcode)
288 | {
289 | SKPaint skPaint = new SKPaint
290 | {
291 | Style = SKPaintStyle.Stroke,
292 | Color = SKColors.Blue,
293 | StrokeWidth = 10,
294 | };
295 |
296 | SKPaint textPaint = new SKPaint
297 | {
298 | Style = SKPaintStyle.Stroke,
299 | Color = SKColors.Blue,
300 | TextSize = (float)(16 * density),
301 | StrokeWidth = 1,
302 | };
303 |
304 | foreach (BarcodeQrData barcodeQrData in barcodeData)
305 | {
306 | canvas.DrawText(barcodeQrData.Reference.Text, barcodeQrData.Points[0], textPaint);
307 | canvas.DrawLine(barcodeQrData.Points[0], barcodeQrData.Points[1], skPaint);
308 | canvas.DrawLine(barcodeQrData.Points[1], barcodeQrData.Points[2], skPaint);
309 | canvas.DrawLine(barcodeQrData.Points[2], barcodeQrData.Points[3], skPaint);
310 | canvas.DrawLine(barcodeQrData.Points[3], barcodeQrData.Points[0], skPaint);
311 | }
312 | }
313 |
314 | if (documentData != null && cameraView.EnableDocumentDetect)
315 | {
316 | SKPaint skPaint = new SKPaint
317 | {
318 | Style = SKPaintStyle.Stroke,
319 | Color = SKColors.Red,
320 | StrokeWidth = 10,
321 | };
322 |
323 | SKPaint textPaint = new SKPaint
324 | {
325 | Style = SKPaintStyle.Stroke,
326 | Color = SKColors.Red,
327 | TextSize = (float)(16 * density),
328 | StrokeWidth = 1,
329 | };
330 |
331 | canvas.DrawText("Detected Document", documentData.Points[0], textPaint);
332 | canvas.DrawLine(documentData.Points[0], documentData.Points[1], skPaint);
333 | canvas.DrawLine(documentData.Points[1], documentData.Points[2], skPaint);
334 | canvas.DrawLine(documentData.Points[2], documentData.Points[3], skPaint);
335 | canvas.DrawLine(documentData.Points[3], documentData.Points[0], skPaint);
336 | }
337 |
338 | if (mrzData != null && cameraView.EnableMrz)
339 | {
340 | SKPaint skPaint = new SKPaint
341 | {
342 | Style = SKPaintStyle.Stroke,
343 | Color = SKColors.Yellow,
344 | StrokeWidth = 10,
345 | };
346 |
347 | SKPaint textPaint = new SKPaint
348 | {
349 | Style = SKPaintStyle.Stroke,
350 | Color = SKColors.Yellow,
351 | TextSize = (float)(16 * density),
352 | StrokeWidth = 1,
353 | };
354 |
355 | foreach (SKPoint[] line in mrzData.Points) {
356 | canvas.DrawLine(line[0], line[1], skPaint);
357 | canvas.DrawLine(line[1], line[2], skPaint);
358 | canvas.DrawLine(line[2], line[3], skPaint);
359 | canvas.DrawLine(line[3], line[0], skPaint);
360 | }
361 |
362 | SKPoint start = mrzData.Points[0][0];
363 | int delta = 20;
364 | start.X += 200;
365 | start.Y -= 200;
366 | canvas.DrawText(mrzData.Reference.Type, start, textPaint);
367 | start.Y += delta;
368 | canvas.DrawText(mrzData.Reference.Nationality, start, textPaint);
369 | start.Y += delta;
370 | canvas.DrawText(mrzData.Reference.Surname, start, textPaint);
371 | start.Y += delta;
372 | canvas.DrawText(mrzData.Reference.GivenName, start, textPaint);
373 | start.Y += delta;
374 | canvas.DrawText(mrzData.Reference.PassportNumber, start, textPaint);
375 | start.Y += delta;
376 | canvas.DrawText(mrzData.Reference.IssuingCountry, start, textPaint);
377 | start.Y += delta;
378 | canvas.DrawText(mrzData.Reference.BirthDate, start, textPaint);
379 | start.Y += delta;
380 | canvas.DrawText(mrzData.Reference.Gender, start, textPaint);
381 | start.Y += delta;
382 | canvas.DrawText(mrzData.Reference.Expiration, start, textPaint);
383 | }
384 | }
385 | }
386 | }
--------------------------------------------------------------------------------
/Capture.Vision.Maui/Platforms/iOS/NativeCameraView.cs:
--------------------------------------------------------------------------------
1 | using AVFoundation;
2 | using CoreAnimation;
3 | using CoreFoundation;
4 | using CoreMedia;
5 | using CoreVideo;
6 | using Dynamsoft;
7 | using Foundation;
8 | using UIKit;
9 | using static Capture.Vision.Maui.CameraInfo;
10 | using static Dynamsoft.DocumentScanner;
11 | namespace Capture.Vision.Maui.Platforms.iOS
12 | {
13 |
14 | internal class NativeCameraView : UIView, IAVCaptureVideoDataOutputSampleBufferDelegate, IAVCapturePhotoCaptureDelegate
15 | {
16 | private AVCaptureDevice[] camDevices;
17 | private readonly CameraView cameraView;
18 | private readonly AVCaptureVideoPreviewLayer PreviewLayer;
19 | private readonly AVCaptureVideoDataOutput videoDataOutput;
20 | private readonly AVCaptureSession captureSession;
21 | private bool started = false;
22 | private readonly DispatchQueue cameraDispacher;
23 | private bool initiated = false;
24 | private BarcodeQRCodeReader barcodeReader;
25 | private MrzScanner mrzScanner;
26 | private DocumentScanner documentScanner;
27 | private DispatchQueue queue = new DispatchQueue("ReadTask", true);
28 | private NSData buffer;
29 | private volatile bool ready = true;
30 | public nint width;
31 | public nint height;
32 | private nint bpr;
33 | private BarcodeQRCodeReader.Result[] results;
34 | private AVCaptureDevice captureDevice;
35 | private AVCaptureInput captureInput = null;
36 |
37 | public NativeCameraView(CameraView cameraView)
38 | {
39 | this.cameraView = cameraView;
40 |
41 | captureSession = new AVCaptureSession
42 | {
43 | SessionPreset = AVCaptureSession.PresetPhoto
44 | };
45 | PreviewLayer = new(captureSession)
46 | {
47 | VideoGravity = AVLayerVideoGravity.ResizeAspectFill
48 | };
49 | Layer.AddSublayer(PreviewLayer);
50 | videoDataOutput = new AVCaptureVideoDataOutput();
51 | var videoSettings = NSDictionary.FromObjectAndKey(
52 | new NSNumber((int)CVPixelFormatType.CV32BGRA),
53 | CVPixelBuffer.PixelFormatTypeKey);
54 | videoDataOutput.WeakVideoSettings = videoSettings;
55 | videoDataOutput.AlwaysDiscardsLateVideoFrames = true;
56 | cameraDispacher = new DispatchQueue("CameraDispacher");
57 |
58 | videoDataOutput.SetSampleBufferDelegate(this, cameraDispacher);
59 | NSNotificationCenter.DefaultCenter.AddObserver(UIDevice.OrientationDidChangeNotification, OrientationChanged);
60 | InitCameras();
61 |
62 | barcodeReader = BarcodeQRCodeReader.Create();
63 | if (cameraView.BarcodeParameters != null)
64 | {
65 | barcodeReader.SetParameters(cameraView.BarcodeParameters);
66 | }
67 |
68 | mrzScanner = MrzScanner.Create();
69 | documentScanner = DocumentScanner.Create();
70 | }
71 |
72 | private void ReadTask()
73 | {
74 | try
75 | {
76 | if (buffer != null)
77 | {
78 | byte[] bytearray = buffer.ToArray();
79 | cameraView.NotifyFrameReady(bytearray, (int)width, (int)height, (int)bpr, FrameReadyEventArgs.PixelFormat.RGBA8888);
80 | if (cameraView.EnableBarcode)
81 | {
82 | results = barcodeReader.DecodeBuffer(bytearray,
83 | (int)width,
84 | (int)height,
85 | (int)bpr,
86 | BarcodeQRCodeReader.ImagePixelFormat.IPF_ARGB_8888);
87 | BarcodeResult[] barcodeResults = new BarcodeResult[0];
88 | if (results != null && results.Length > 0)
89 | {
90 | barcodeResults = new BarcodeResult[results.Length];
91 |
92 | for (int i = 0; i < results.Length; i++)
93 | {
94 | barcodeResults[i] = new BarcodeResult()
95 | {
96 | Text = results[i].Text,
97 | Points = results[i].Points,
98 | Format1 = results[i].Format1,
99 | Format2 = results[i].Format2
100 | };
101 | }
102 | }
103 | cameraView.NotifyResultReady(barcodeResults, (int)width, (int)height);
104 | }
105 |
106 | if (cameraView.EnableDocumentDetect)
107 | {
108 | DocumentScanner.Result[] results = documentScanner.DetectBuffer(bytearray,
109 | (int)width,
110 | (int)height,
111 | (int)bpr,
112 | DocumentScanner.ImagePixelFormat.IPF_ARGB_8888);
113 | DocumentResult documentResults = new DocumentResult();
114 | if (results != null && results.Length > 0)
115 | {
116 | documentResults = new DocumentResult
117 | {
118 | Confidence = results[0].Confidence,
119 | Points = results[0].Points
120 | };
121 |
122 | if (cameraView.EnableDocumentRectify)
123 | {
124 | NormalizedImage normalizedImage = documentScanner.NormalizeBuffer(bytearray,
125 | (int)width,
126 | (int)height,
127 | (int)bpr,
128 | DocumentScanner.ImagePixelFormat.IPF_ARGB_8888, documentResults.Points);
129 | documentResults.Width = normalizedImage.Width;
130 | documentResults.Height = normalizedImage.Height;
131 | documentResults.Stride = normalizedImage.Stride;
132 | documentResults.Format = normalizedImage.Format;
133 | documentResults.Data = normalizedImage.Data;
134 | }
135 | }
136 |
137 | cameraView.NotifyResultReady(documentResults, (int)width, (int)height);
138 |
139 | }
140 |
141 | if (cameraView.EnableMrz)
142 | {
143 | MrzResult mrzResults = new MrzResult();
144 | try
145 | {
146 | MrzScanner.Result[] results = mrzScanner.DetectBuffer(bytearray,
147 | (int)width,
148 | (int)height,
149 | (int)bpr,
150 | MrzScanner.ImagePixelFormat.IPF_ARGB_8888);
151 |
152 |
153 | if (results != null && results.Length > 0)
154 | {
155 | Line[] rawData = new Line[results.Length];
156 | string[] lines = new string[results.Length];
157 |
158 | for (int i = 0; i < results.Length; i++)
159 | {
160 | rawData[i] = new Line()
161 | {
162 | Confidence = results[i].Confidence,
163 | Text = results[i].Text,
164 | Points = results[i].Points,
165 | };
166 | lines[i] = results[i].Text;
167 | }
168 |
169 |
170 | Dynamsoft.MrzResult info = MrzParser.Parse(lines);
171 | mrzResults = new MrzResult()
172 | {
173 | RawData = rawData,
174 | Type = info.Type,
175 | Nationality = info.Nationality,
176 | Surname = info.Surname,
177 | GivenName = info.GivenName,
178 | PassportNumber = info.PassportNumber,
179 | IssuingCountry = info.IssuingCountry,
180 | BirthDate = info.BirthDate,
181 | Gender = info.Gender,
182 | Expiration = info.Expiration,
183 | Lines = info.Lines
184 | };
185 |
186 | }
187 | }
188 |
189 | catch (Exception ex)
190 | {
191 | System.Diagnostics.Debug.WriteLine(ex.Message);
192 | }
193 |
194 | cameraView.NotifyResultReady(mrzResults, (int)width, (int)height);
195 | }
196 | }
197 |
198 | ready = true;
199 | }
200 | catch (Exception ex)
201 | {
202 | System.Diagnostics.Debug.WriteLine(ex.Message);
203 | }
204 | }
205 |
206 | private void OrientationChanged(NSNotification notification)
207 | {
208 | LayoutSubviews();
209 | }
210 | private void InitCameras()
211 | {
212 | if (!initiated)
213 | {
214 | try
215 | {
216 | var deviceDescoverySession = AVCaptureDeviceDiscoverySession.Create(new AVCaptureDeviceType[] { AVCaptureDeviceType.BuiltInWideAngleCamera }, AVMediaTypes.Video, AVCaptureDevicePosition.Unspecified);
217 | camDevices = deviceDescoverySession.Devices;
218 | cameraView.Cameras.Clear();
219 | foreach (var device in camDevices)
220 | {
221 | Position position = device.Position switch
222 | {
223 | AVCaptureDevicePosition.Back => Position.Back,
224 | AVCaptureDevicePosition.Front => Position.Front,
225 | _ => Position.Unknown
226 | };
227 | cameraView.Cameras.Add(new CameraInfo
228 | {
229 | Name = device.LocalizedName,
230 | DeviceId = device.UniqueID,
231 | Pos = position,
232 | AvailableResolutions = new() { new(1920, 1080), new(1280, 720), new(640, 480), new(352, 288) }
233 | });
234 | }
235 | deviceDescoverySession.Dispose();
236 | initiated = true;
237 | cameraView.UpdateCameras();
238 | }
239 | catch
240 | {
241 | }
242 | }
243 | }
244 |
245 | public async Task StartCameraAsync()
246 | {
247 | Status result = Status.Unavailable;
248 | if (initiated)
249 | {
250 | if (started) StopCamera();
251 | if (await CameraView.RequestPermissions())
252 | {
253 | if (cameraView.Camera != null && captureSession != null)
254 | {
255 | try
256 | {
257 | Size FrameSize = new(0, 0);
258 | captureSession.SessionPreset = FrameSize.Width switch
259 | {
260 | 352 => AVCaptureSession.Preset352x288,
261 | 640 => AVCaptureSession.Preset640x480,
262 | 1280 => AVCaptureSession.Preset1280x720,
263 | 1920 => AVCaptureSession.Preset1920x1080,
264 | _ => AVCaptureSession.PresetPhoto
265 | };
266 | captureDevice = camDevices.First(d => d.UniqueID == cameraView.Camera.DeviceId);
267 | captureInput = new AVCaptureDeviceInput(captureDevice, out var err);
268 | captureSession.AddInput(captureInput);
269 | captureSession.AddOutput(videoDataOutput);
270 | captureSession.StartRunning();
271 | started = true;
272 | result = Status.Available;
273 | }
274 | catch
275 | {
276 | }
277 | }
278 |
279 | }
280 |
281 | }
282 | return result;
283 | }
284 | public Status StopCamera()
285 | {
286 | Status result = Status.Available;
287 | if (initiated)
288 | {
289 | try
290 | {
291 | if (captureSession != null)
292 | {
293 | if (captureSession.Running)
294 | captureSession.StopRunning();
295 |
296 | foreach (var output in captureSession.Outputs)
297 | captureSession.RemoveOutput(output);
298 | foreach (var input in captureSession.Inputs)
299 | {
300 | captureSession.RemoveInput(input);
301 | input.Dispose();
302 | }
303 | }
304 | started = false;
305 | }
306 | catch
307 | {
308 | result = Status.Unavailable;
309 | }
310 | }
311 | else
312 | result = Status.Unavailable;
313 |
314 | return result;
315 | }
316 | public void DisposeControl()
317 | {
318 | if (started) StopCamera();
319 | NSNotificationCenter.DefaultCenter.RemoveObserver(UIDevice.OrientationDidChangeNotification);
320 | PreviewLayer?.Dispose();
321 | captureSession?.Dispose();
322 | Dispose();
323 | }
324 |
325 | [Export("captureOutput:didOutputSampleBuffer:fromConnection:")]
326 | public void DidOutputSampleBuffer(AVCaptureOutput captureOutput, CMSampleBuffer sampleBuffer, AVCaptureConnection connection)
327 | {
328 | if (ready)
329 | {
330 | ready = false;
331 | CVPixelBuffer cVPixelBuffer = (CVPixelBuffer)sampleBuffer.GetImageBuffer();
332 |
333 | cVPixelBuffer.Lock(CVPixelBufferLock.ReadOnly);
334 | nint dataSize = cVPixelBuffer.DataSize;
335 | width = cVPixelBuffer.Width;
336 | height = cVPixelBuffer.Height;
337 | IntPtr baseAddress = cVPixelBuffer.BaseAddress;
338 | bpr = cVPixelBuffer.BytesPerRow;
339 | cVPixelBuffer.Unlock(CVPixelBufferLock.ReadOnly);
340 | buffer = NSData.FromBytes(baseAddress, (nuint)dataSize);
341 | cVPixelBuffer.Dispose();
342 | queue.DispatchAsync(ReadTask);
343 | }
344 | sampleBuffer.Dispose();
345 | }
346 |
347 | public override void LayoutSubviews()
348 | {
349 | base.LayoutSubviews();
350 | CATransform3D transform = CATransform3D.MakeRotation(0, 0, 0, 1.0f);
351 | switch (UIDevice.CurrentDevice.Orientation)
352 | {
353 | case UIDeviceOrientation.Portrait:
354 | transform = CATransform3D.MakeRotation(0, 0, 0, 1.0f);
355 | break;
356 | case UIDeviceOrientation.PortraitUpsideDown:
357 | transform = CATransform3D.MakeRotation((nfloat)Math.PI, 0, 0, 1.0f);
358 | break;
359 | case UIDeviceOrientation.LandscapeLeft:
360 | transform = CATransform3D.MakeRotation((nfloat)(-Math.PI / 2), 0, 0, 1.0f);
361 | break;
362 | case UIDeviceOrientation.LandscapeRight:
363 | transform = CATransform3D.MakeRotation((nfloat)Math.PI / 2, 0, 0, 1.0f);
364 | break;
365 | }
366 |
367 | PreviewLayer.Transform = transform;
368 | PreviewLayer.Frame = Layer.Bounds;
369 | }
370 | }
371 |
372 |
373 | }
--------------------------------------------------------------------------------
/Capture.Vision.Maui/Platforms/Windows/NativeCameraView.cs:
--------------------------------------------------------------------------------
1 | using Panel = Windows.Devices.Enumeration.Panel;
2 | using Microsoft.UI.Xaml.Controls;
3 | using Windows.Media.Capture.Frames;
4 | using Windows.Media.Capture;
5 | using Windows.Devices.Enumeration;
6 | using Windows.Media.Core;
7 | using Windows.Graphics.Imaging;
8 | using System.Runtime.InteropServices.WindowsRuntime;
9 | using System.Collections.Concurrent;
10 | using Dynamsoft;
11 | using static Capture.Vision.Maui.CameraInfo;
12 | using static Dynamsoft.BarcodeQRCodeReader;
13 | using static Dynamsoft.MrzScanner;
14 | using static Dynamsoft.DocumentScanner;
15 |
16 | namespace Capture.Vision.Maui.Platforms.Windows
17 | {
18 | public sealed partial class NativeCameraView : UserControl, IDisposable
19 | {
20 | private Microsoft.UI.Xaml.FlowDirection flowDirection = Microsoft.UI.Xaml.FlowDirection.LeftToRight;
21 | private BarcodeQRCodeReader barcodeReader;
22 | private DocumentScanner documentScanner;
23 | private MrzScanner mrzScanner;
24 | private Thread thread;
25 | private static object lockObject = new object();
26 | private ConcurrentQueue _bitmapQueue = new ConcurrentQueue();
27 | private volatile bool isCapturing;
28 | private readonly MediaPlayerElement mediaElement;
29 | private MediaCapture mediaCapture;
30 | private MediaFrameSource frameSource;
31 | private MediaFrameReader frameReader;
32 | private List sourceGroups;
33 | private bool started = false, initiated = false;
34 | private readonly CameraView cameraView;
35 |
36 | public NativeCameraView(CameraView cameraView)
37 | {
38 | this.cameraView = cameraView;
39 | mediaElement = new MediaPlayerElement
40 | {
41 | HorizontalAlignment = Microsoft.UI.Xaml.HorizontalAlignment.Stretch,
42 | VerticalAlignment = Microsoft.UI.Xaml.VerticalAlignment.Stretch
43 | };
44 | Content = mediaElement;
45 | InitCameras();
46 |
47 | barcodeReader = BarcodeQRCodeReader.Create();
48 |
49 | if (cameraView.BarcodeParameters != null)
50 | {
51 | barcodeReader.SetParameters(cameraView.BarcodeParameters);
52 | }
53 |
54 | documentScanner = DocumentScanner.Create();
55 |
56 | mrzScanner = MrzScanner.Create();
57 | }
58 |
59 | private void ProcessFrames()
60 | {
61 | while (isCapturing)
62 | {
63 | SoftwareBitmap bitmap;
64 | bool ret = _bitmapQueue.TryDequeue(out bitmap);
65 | if (ret)
66 | {
67 | byte[] buffer = new byte[bitmap.PixelWidth * bitmap.PixelHeight];
68 | bitmap.CopyToBuffer(buffer.AsBuffer());
69 | cameraView.NotifyFrameReady(buffer, bitmap.PixelWidth, bitmap.PixelHeight, bitmap.PixelWidth, FrameReadyEventArgs.PixelFormat.GRAYSCALE);
70 | if (cameraView.EnableBarcode)
71 | {
72 | BarcodeQRCodeReader.Result[] results = barcodeReader.DecodeBuffer(buffer, bitmap.PixelWidth, bitmap.PixelHeight, bitmap.PixelWidth, BarcodeQRCodeReader.ImagePixelFormat.IPF_GRAYSCALED);
73 | BarcodeResult[] barcodeResults = new BarcodeResult[0];
74 | if (results != null && results.Length > 0)
75 | {
76 | barcodeResults = new BarcodeResult[results.Length];
77 |
78 | for (int i = 0; i < results.Length; i++)
79 | {
80 | barcodeResults[i] = new BarcodeResult()
81 | {
82 | Text = results[i].Text,
83 | Points = results[i].Points,
84 | Format1 = results[i].Format1,
85 | Format2 = results[i].Format2
86 | };
87 | }
88 | }
89 | cameraView.NotifyResultReady(barcodeResults, bitmap.PixelWidth, bitmap.PixelHeight);
90 | }
91 |
92 | if (cameraView.EnableDocumentDetect)
93 | {
94 | DocumentScanner.Result[] results = documentScanner.DetectBuffer(buffer, bitmap.PixelWidth, bitmap.PixelHeight, bitmap.PixelWidth, DocumentScanner.ImagePixelFormat.IPF_GRAYSCALED);
95 | DocumentResult documentResults = new DocumentResult();
96 | if (results != null && results.Length > 0)
97 | {
98 | documentResults = new DocumentResult
99 | {
100 | Confidence = results[0].Confidence,
101 | Points = results[0].Points
102 | };
103 |
104 | if (cameraView.EnableDocumentRectify)
105 | {
106 | NormalizedImage image = documentScanner.NormalizeBuffer(buffer, bitmap.PixelWidth, bitmap.PixelHeight, bitmap.PixelWidth, DocumentScanner.ImagePixelFormat.IPF_GRAYSCALED, documentResults.Points);
107 | documentResults.Width = image.Width;
108 | documentResults.Height = image.Height;
109 | documentResults.Stride = image.Stride;
110 | documentResults.Format = image.Format;
111 | documentResults.Data = image.Data;
112 | }
113 | }
114 |
115 | cameraView.NotifyResultReady(documentResults, bitmap.PixelWidth, bitmap.PixelHeight);
116 |
117 | }
118 |
119 | if (cameraView.EnableMrz)
120 | {
121 | MrzScanner.Result[] results = mrzScanner.DetectBuffer(buffer, bitmap.PixelWidth, bitmap.PixelHeight, bitmap.PixelWidth, MrzScanner.ImagePixelFormat.IPF_GRAYSCALED);
122 | MrzResult mrzResults = new MrzResult();
123 |
124 | if (results != null && results.Length > 0)
125 | {
126 | Line[] rawData = new Line[results.Length];
127 | string[] lines = new string[results.Length];
128 |
129 | for (int i = 0; i < results.Length; i++)
130 | {
131 | rawData[i] = new Line()
132 | {
133 | Confidence = results[i].Confidence,
134 | Text = results[i].Text,
135 | Points = results[i].Points,
136 | };
137 | lines[i] = results[i].Text;
138 | }
139 |
140 | try
141 | {
142 | Dynamsoft.MrzResult info = MrzParser.Parse(lines);
143 | mrzResults = new MrzResult()
144 | {
145 | RawData = rawData,
146 | Type = info.Type,
147 | Nationality = info.Nationality,
148 | Surname = info.Surname,
149 | GivenName = info.GivenName,
150 | PassportNumber = info.PassportNumber,
151 | IssuingCountry = info.IssuingCountry,
152 | BirthDate = info.BirthDate,
153 | Gender = info.Gender,
154 | Expiration = info.Expiration,
155 | Lines = info.Lines
156 | };
157 | }
158 | catch (Exception ex) {
159 |
160 | }
161 | }
162 |
163 | cameraView.NotifyResultReady(mrzResults, bitmap.PixelWidth, bitmap.PixelHeight);
164 | }
165 |
166 | bitmap.Dispose();
167 | }
168 | }
169 | }
170 |
171 | private void Create()
172 | {
173 | lock (lockObject)
174 | {
175 | isCapturing = true;
176 | thread = new Thread(new ThreadStart(ProcessFrames));
177 | thread.Start();
178 | }
179 | }
180 |
181 | private void InitCameras()
182 | {
183 | if (!initiated)
184 | {
185 | try
186 | {
187 | var devices = DeviceInformation.FindAllAsync(DeviceClass.VideoCapture).GetAwaiter().GetResult();
188 | var allSourceGroups = MediaFrameSourceGroup.FindAllAsync().GetAwaiter().GetResult();
189 | sourceGroups = allSourceGroups.Where(g => g.SourceInfos.Any(s => s.SourceKind == MediaFrameSourceKind.Color &&
190 | (s.MediaStreamType == MediaStreamType.VideoPreview || s.MediaStreamType == MediaStreamType.VideoRecord))
191 | && g.SourceInfos.All(sourceInfo => devices.Any(device => device.Id == sourceInfo.DeviceInformation.Id))).ToList();
192 | cameraView.Cameras.Clear();
193 | foreach (var sourceGroup in sourceGroups)
194 | {
195 | Position position = Position.Unknown;
196 | var device = devices.FirstOrDefault(device => device.Id == sourceGroup.Id);
197 | if (device != null)
198 | {
199 | if (device.EnclosureLocation != null)
200 | position = device.EnclosureLocation.Panel switch
201 | {
202 | Panel.Front => Position.Front,
203 | Panel.Back => Position.Back,
204 | _ => Position.Unknown
205 | };
206 | }
207 |
208 | var camInfo = new CameraInfo
209 | {
210 | Name = sourceGroup.DisplayName,
211 | DeviceId = sourceGroup.Id,
212 | Pos = position,
213 | AvailableResolutions = new()
214 | };
215 | foreach (var profile in MediaCapture.FindAllVideoProfiles(sourceGroup.Id))
216 | {
217 | foreach (var recordMediaP in profile.SupportedRecordMediaDescription)
218 | {
219 | if (!camInfo.AvailableResolutions.Any(s => s.Width == recordMediaP.Width && s.Height == recordMediaP.Height))
220 | camInfo.AvailableResolutions.Add(new(recordMediaP.Width, recordMediaP.Height));
221 | }
222 | }
223 | cameraView.Cameras.Add(camInfo);
224 | }
225 |
226 | initiated = true;
227 | cameraView.UpdateCameras();
228 |
229 | Create();
230 | }
231 | catch
232 | {
233 | }
234 | }
235 | }
236 |
237 |
238 | internal async Task StartCameraAsync()
239 | {
240 | Status result = Status.Unavailable;
241 |
242 | if (initiated)
243 | {
244 | if (started) await StopCameraAsync();
245 | if (cameraView.Camera != null)
246 | {
247 | started = true;
248 | mediaCapture = new MediaCapture();
249 | try
250 | {
251 | await mediaCapture.InitializeAsync(new MediaCaptureInitializationSettings
252 | {
253 | SourceGroup = sourceGroups.First(source => source.Id == cameraView.Camera.DeviceId),
254 | MemoryPreference = MediaCaptureMemoryPreference.Cpu,
255 | StreamingCaptureMode = StreamingCaptureMode.Video
256 | });
257 | frameSource = mediaCapture.FrameSources.FirstOrDefault(source => source.Value.Info.MediaStreamType == MediaStreamType.VideoRecord
258 | && source.Value.Info.SourceKind == MediaFrameSourceKind.Color).Value;
259 | if (frameSource != null)
260 | {
261 | MediaFrameFormat frameFormat;
262 | frameFormat = frameSource.SupportedFormats.OrderByDescending(f => f.VideoFormat.Width * f.VideoFormat.Height).FirstOrDefault();
263 |
264 | if (frameFormat != null)
265 | {
266 | await frameSource.SetFormatAsync(frameFormat);
267 | mediaElement.AutoPlay = true;
268 | mediaElement.Source = MediaSource.CreateFromMediaFrameSource(frameSource);
269 | mediaElement.FlowDirection = flowDirection;
270 |
271 | frameReader = await mediaCapture.CreateFrameReaderAsync(frameSource);
272 | frameReader.AcquisitionMode = MediaFrameReaderAcquisitionMode.Realtime;
273 | if (frameReader != null)
274 | {
275 | frameReader.FrameArrived += OnFrameAvailable;
276 | var status = await frameReader.StartAsync();
277 | if (status == MediaFrameReaderStartStatus.Success)
278 | {
279 | result = Status.Available;
280 | }
281 | }
282 |
283 | }
284 | }
285 | }
286 | catch
287 | {
288 | }
289 | }
290 |
291 | if (result != Status.Available && mediaCapture != null)
292 | {
293 | if (frameReader != null)
294 | {
295 | frameReader.FrameArrived -= OnFrameAvailable;
296 | frameReader.Dispose();
297 | frameReader = null;
298 | }
299 | mediaCapture.Dispose();
300 | mediaCapture = null;
301 | }
302 | }
303 |
304 | return result;
305 | }
306 |
307 | private void OnFrameAvailable(MediaFrameReader sender, MediaFrameArrivedEventArgs args)
308 | {
309 | var frame = sender.TryAcquireLatestFrame();
310 | if (frame == null) return;
311 |
312 | SoftwareBitmap bitmap = frame.VideoMediaFrame.SoftwareBitmap;
313 | if (_bitmapQueue.Count == 2) ClearQueue();
314 | SoftwareBitmap grayscale = SoftwareBitmap.Convert(bitmap, BitmapPixelFormat.Gray8, BitmapAlphaMode.Ignore);
315 | _bitmapQueue.Enqueue(grayscale);
316 | bitmap.Dispose();
317 | }
318 |
319 | internal async Task StopCameraAsync()
320 | {
321 | Status result = Status.Available;
322 | if (initiated)
323 | {
324 | try
325 | {
326 | if (frameReader != null)
327 | {
328 | await frameReader.StopAsync();
329 | frameReader.FrameArrived -= OnFrameAvailable;
330 | frameReader?.Dispose();
331 | frameReader = null;
332 | }
333 | mediaElement.Source = null;
334 | if (mediaCapture != null)
335 | {
336 | mediaCapture.Dispose();
337 | mediaCapture = null;
338 | }
339 | }
340 | catch
341 | {
342 | result = Status.Unavailable;
343 | }
344 | }
345 | else
346 | result = Status.Unavailable;
347 | started = false;
348 | Destroy();
349 | return result;
350 | }
351 | internal void DisposeControl()
352 | {
353 | if (started) StopCameraAsync().Wait();
354 | Dispose();
355 | }
356 |
357 | public void Dispose()
358 | {
359 | Destroy();
360 | StopCameraAsync().Wait();
361 | }
362 |
363 | public void Destroy()
364 | {
365 | lock (lockObject)
366 | {
367 | if (thread != null)
368 | {
369 | isCapturing = false;
370 |
371 | thread.Join();
372 | thread = null;
373 | }
374 |
375 | ClearQueue();
376 | }
377 | }
378 |
379 | private void ClearQueue()
380 | {
381 | while (_bitmapQueue.Count > 0)
382 | {
383 | SoftwareBitmap bitmap;
384 | _bitmapQueue.TryDequeue(out bitmap);
385 | bitmap.Dispose();
386 | }
387 | }
388 | }
389 | }
--------------------------------------------------------------------------------
/Capture.Vision.Maui/Platforms/Android/NativeCameraView.cs:
--------------------------------------------------------------------------------
1 | using Java.Util.Concurrent;
2 | using Android.Content;
3 | using Android.Widget;
4 | using Android.Graphics;
5 | using Android.Hardware.Camera2;
6 | using Android.Media;
7 | using Image = Android.Media.Image;
8 | using Android.Views;
9 | using Android.Util;
10 | using Android.Hardware.Camera2.Params;
11 | using Android.Runtime;
12 | using Android.OS;
13 | using CameraCharacteristics = Android.Hardware.Camera2.CameraCharacteristics;
14 | using Size = Android.Util.Size;
15 | using Class = Java.Lang.Class;
16 | using static Capture.Vision.Maui.CameraInfo;
17 | using Dynamsoft;
18 | using static Dynamsoft.BarcodeQRCodeReader;
19 | using static Dynamsoft.MrzScanner;
20 | using Java.Nio;
21 | using static Dynamsoft.DocumentScanner;
22 |
23 | namespace Capture.Vision.Maui.Platforms.Android
24 | {
25 | internal class NativeCameraView : FrameLayout
26 | {
27 | private readonly CameraView cameraView;
28 | private IExecutorService executorService;
29 | private bool started = false;
30 | private bool initiated = false;
31 | private readonly Context context;
32 | private readonly TextureView textureView;
33 | public CameraCaptureSession previewSession;
34 | private CaptureRequest.Builder previewBuilder;
35 | private CameraDevice cameraDevice;
36 | private readonly CameraStateCallback stateListener;
37 | private Size videoSize;
38 | private CameraManager cameraManager;
39 | private readonly SparseIntArray ORIENTATIONS = new();
40 | private CameraCharacteristics camChars;
41 | private PreviewCaptureStateCallback sessionCallback;
42 | private ImageAvailableListener frameListener;
43 | private HandlerThread backgroundThread;
44 | private Handler backgroundHandler;
45 | private ImageReader imageReader;
46 | private BarcodeQRCodeReader barcodeReader;
47 | private MrzScanner mrzScanner;
48 | private DocumentScanner documentScanner;
49 | public NativeCameraView(Context context, CameraView cameraView) : base(context)
50 | {
51 | this.context = context;
52 | this.cameraView = cameraView;
53 |
54 | textureView = new(context);
55 | stateListener = new CameraStateCallback(this);
56 |
57 | AddView(textureView);
58 | ORIENTATIONS.Append((int)SurfaceOrientation.Rotation0, 90);
59 | ORIENTATIONS.Append((int)SurfaceOrientation.Rotation90, 0);
60 | ORIENTATIONS.Append((int)SurfaceOrientation.Rotation180, 270);
61 | ORIENTATIONS.Append((int)SurfaceOrientation.Rotation270, 180);
62 | InitCameras();
63 |
64 | barcodeReader = BarcodeQRCodeReader.Create();
65 | if (cameraView.BarcodeParameters != null)
66 | {
67 | barcodeReader.SetParameters(cameraView.BarcodeParameters);
68 | }
69 |
70 | mrzScanner = MrzScanner.Create();
71 | documentScanner = DocumentScanner.Create();
72 | }
73 |
74 | private void InitCameras()
75 | {
76 | if (!initiated && cameraView != null)
77 | {
78 | cameraManager = (CameraManager)context.GetSystemService(Context.CameraService);
79 | cameraView.Cameras.Clear();
80 | foreach (var id in cameraManager.GetCameraIdList())
81 | {
82 | var cameraInfo = new CameraInfo { DeviceId = id };
83 | var chars = cameraManager.GetCameraCharacteristics(id);
84 | if ((int)(chars.Get(CameraCharacteristics.LensFacing) as Java.Lang.Number) == (int)LensFacing.Back)
85 | {
86 | cameraInfo.Name = "Back Camera";
87 | cameraInfo.Pos = Position.Back;
88 | }
89 | else if ((int)(chars.Get(CameraCharacteristics.LensFacing) as Java.Lang.Number) == (int)LensFacing.Front)
90 | {
91 | cameraInfo.Name = "Front Camera";
92 | cameraInfo.Pos = Position.Front;
93 | }
94 | else
95 | {
96 | cameraInfo.Name = "Camera " + id;
97 | cameraInfo.Pos = Position.Unknown;
98 | }
99 | StreamConfigurationMap map = (StreamConfigurationMap)chars.Get(CameraCharacteristics.ScalerStreamConfigurationMap);
100 | cameraInfo.AvailableResolutions = new();
101 | foreach (var s in map.GetOutputSizes(Class.FromType(typeof(ImageReader))))
102 | cameraInfo.AvailableResolutions.Add(new(s.Width, s.Height));
103 | cameraView.Cameras.Add(cameraInfo);
104 | }
105 | executorService = Executors.NewSingleThreadExecutor();
106 |
107 | initiated = true;
108 | cameraView.UpdateCameras();
109 | }
110 | }
111 |
112 | private void StartPreview()
113 | {
114 | while (textureView.SurfaceTexture == null) Thread.Sleep(100);
115 | SurfaceTexture texture = textureView.SurfaceTexture;
116 | texture.SetDefaultBufferSize(videoSize.Width, videoSize.Height);
117 |
118 | previewBuilder = cameraDevice.CreateCaptureRequest(CameraTemplate.Preview);
119 | var surfaces = new List();
120 | var previewSurface = new Surface(texture);
121 | surfaces.Add(new OutputConfiguration(previewSurface));
122 | previewBuilder.AddTarget(previewSurface);
123 |
124 | imageReader = ImageReader.NewInstance(videoSize.Width, videoSize.Height, ImageFormatType.Yuv420888, 1);
125 | backgroundThread = new HandlerThread("CameraBackground");
126 | backgroundThread.Start();
127 | backgroundHandler = new Handler(backgroundThread.Looper);
128 | frameListener = new ImageAvailableListener(cameraView, barcodeReader, mrzScanner, documentScanner);
129 | imageReader.SetOnImageAvailableListener(frameListener, backgroundHandler);
130 | surfaces.Add(new OutputConfiguration(imageReader.Surface));
131 | previewBuilder.AddTarget(imageReader.Surface);
132 |
133 | sessionCallback = new PreviewCaptureStateCallback(this);
134 | SessionConfiguration config = new((int)SessionType.Regular, surfaces, executorService, sessionCallback);
135 | cameraDevice.CreateCaptureSession(config);
136 | }
137 | private void UpdatePreview()
138 | {
139 | if (null == cameraDevice)
140 | return;
141 |
142 | try
143 | {
144 | previewBuilder.Set(CaptureRequest.ControlMode, Java.Lang.Integer.ValueOf((int)ControlMode.Auto));
145 | AdjustAspectRatio(videoSize.Width, videoSize.Height);
146 | previewSession.SetRepeatingRequest(previewBuilder.Build(), null, null);
147 | }
148 | catch (CameraAccessException e)
149 | {
150 | e.PrintStackTrace();
151 | }
152 | }
153 | internal async Task StartCameraAsync()
154 | {
155 | var result = Status.Unavailable;
156 | if (initiated)
157 | {
158 | if (await CameraView.RequestPermissions())
159 | {
160 | if (started) StopCamera();
161 | if (cameraView.Camera != null)
162 | {
163 | try
164 | {
165 | camChars = cameraManager.GetCameraCharacteristics(cameraView.Camera.DeviceId);
166 | StreamConfigurationMap map = (StreamConfigurationMap)camChars.Get(CameraCharacteristics.ScalerStreamConfigurationMap);
167 | videoSize = ChooseVideoSize(map.GetOutputSizes(Class.FromType(typeof(ImageReader))));
168 | cameraManager.OpenCamera(cameraView.Camera.DeviceId, executorService, stateListener);
169 |
170 | started = true;
171 | result = Status.Available;
172 | }
173 | catch
174 | {
175 | }
176 | }
177 | }
178 | }
179 |
180 | return result;
181 | }
182 |
183 | internal Status StopCamera()
184 | {
185 | Status result = Status.Available;
186 | if (initiated)
187 | {
188 | try
189 | {
190 | imageReader?.SetOnImageAvailableListener(null, null);
191 | imageReader?.Dispose();
192 | imageReader = null;
193 | backgroundThread?.QuitSafely();
194 | backgroundThread?.Join();
195 | backgroundThread = null;
196 | backgroundHandler = null;
197 |
198 | }
199 | catch { }
200 | try
201 | {
202 | previewSession?.StopRepeating();
203 | previewSession?.Dispose();
204 | }
205 | catch { }
206 | try
207 | {
208 | cameraDevice?.Close();
209 | cameraDevice?.Dispose();
210 | }
211 | catch { }
212 | previewSession = null;
213 | cameraDevice = null;
214 | previewBuilder = null;
215 | started = false;
216 | }
217 | else
218 | result = Status.Unavailable;
219 |
220 | return result;
221 | }
222 | internal void DisposeControl()
223 | {
224 | try
225 | {
226 | if (started) StopCamera();
227 | executorService?.Shutdown();
228 | executorService?.Dispose();
229 | RemoveAllViews();
230 | textureView?.Dispose();
231 | Dispose();
232 | }
233 | catch { }
234 | }
235 |
236 | private Size ChooseVideoSize(Size[] choices)
237 | {
238 | Size result = new Size(640, 480);
239 | foreach (Size size in choices)
240 | {
241 | if (size.Width == 1280 && size.Height == 720)
242 | {
243 | result = size;
244 | break;
245 | }
246 | }
247 |
248 | return result;
249 | }
250 |
251 | private void AdjustAspectRatio(int videoWidth, int videoHeight)
252 | {
253 | Matrix txform = new();
254 | float scaleX = (float)videoWidth / Width;
255 | float scaleY = (float)videoHeight / Height;
256 | if (IsDimensionSwapped())
257 | {
258 | scaleX = (float)videoHeight / Width;
259 | scaleY = (float)videoWidth / Height;
260 | }
261 | if (scaleX <= scaleY)
262 | {
263 | scaleY /= scaleX;
264 | scaleX = 1;
265 | }
266 | else
267 | {
268 | scaleX /= scaleY;
269 | scaleY = 1;
270 | }
271 | txform.PostScale(scaleX, scaleY, 0, 0);
272 | textureView.SetTransform(txform);
273 | }
274 |
275 | private bool IsDimensionSwapped()
276 | {
277 | IWindowManager windowManager = context.GetSystemService(Context.WindowService).JavaCast();
278 | var displayRotation = windowManager.DefaultDisplay.Rotation;
279 | var chars = cameraManager.GetCameraCharacteristics(cameraView.Camera.DeviceId);
280 | int sensorOrientation = (int)(chars.Get(CameraCharacteristics.SensorOrientation) as Java.Lang.Integer);
281 | bool swappedDimensions = false;
282 | switch (displayRotation)
283 | {
284 | case SurfaceOrientation.Rotation0:
285 | case SurfaceOrientation.Rotation180:
286 | if (sensorOrientation == 90 || sensorOrientation == 270)
287 | {
288 | swappedDimensions = true;
289 | }
290 | break;
291 | case SurfaceOrientation.Rotation90:
292 | case SurfaceOrientation.Rotation270:
293 | if (sensorOrientation == 0 || sensorOrientation == 180)
294 | {
295 | swappedDimensions = true;
296 | }
297 | break;
298 | }
299 | return swappedDimensions;
300 | }
301 |
302 | private class CameraStateCallback : CameraDevice.StateCallback
303 | {
304 | private readonly NativeCameraView cameraView;
305 | public CameraStateCallback(NativeCameraView camView)
306 | {
307 | cameraView = camView;
308 | }
309 | public override void OnOpened(CameraDevice camera)
310 | {
311 | if (camera != null)
312 | {
313 | cameraView.cameraDevice = camera;
314 | cameraView.StartPreview();
315 | }
316 | }
317 |
318 | public override void OnDisconnected(CameraDevice camera)
319 | {
320 | camera.Close();
321 | cameraView.cameraDevice = null;
322 | }
323 |
324 | public override void OnError(CameraDevice camera, CameraError error)
325 | {
326 | camera?.Close();
327 | cameraView.cameraDevice = null;
328 | }
329 | }
330 |
331 | private class PreviewCaptureStateCallback : CameraCaptureSession.StateCallback
332 | {
333 | private readonly NativeCameraView cameraView;
334 | public PreviewCaptureStateCallback(NativeCameraView camView)
335 | {
336 | cameraView = camView;
337 | }
338 | public override void OnConfigured(CameraCaptureSession session)
339 | {
340 | cameraView.previewSession = session;
341 | cameraView.UpdatePreview();
342 |
343 | }
344 | public override void OnConfigureFailed(CameraCaptureSession session)
345 | {
346 | }
347 | }
348 | class ImageAvailableListener : Java.Lang.Object, ImageReader.IOnImageAvailableListener
349 | {
350 | private readonly CameraView cameraView;
351 | internal bool isReady = true;
352 | private BarcodeQRCodeReader barcodeReader;
353 | private MrzScanner mrzScanner;
354 | private DocumentScanner documentScanner;
355 |
356 | public ImageAvailableListener(CameraView camView, BarcodeQRCodeReader barcodeReader, MrzScanner mrzScanner, DocumentScanner documentScanner)
357 | {
358 | cameraView = camView;
359 | this.barcodeReader = barcodeReader;
360 | this.mrzScanner = mrzScanner;
361 | this.documentScanner = documentScanner;
362 | }
363 |
364 | public void OnImageAvailable(ImageReader reader)
365 | {
366 | try
367 | {
368 | var image = reader?.AcquireLatestImage();
369 | if (image == null)
370 | return;
371 |
372 | Image.Plane[] planes = image.GetPlanes();
373 | if (planes == null) return;
374 |
375 | int width = image.Width;
376 | int height = image.Height;
377 | ByteBuffer buffer = planes[0].Buffer;
378 | byte[] bytes = new byte[buffer.Remaining()];
379 | buffer.Get(bytes);
380 | int nRowStride = planes[0].RowStride;
381 | int nPixelStride = planes[0].PixelStride;
382 | image.Close();
383 |
384 | cameraView.NotifyFrameReady(bytes, width, height, nPixelStride * nRowStride, FrameReadyEventArgs.PixelFormat.GRAYSCALE);
385 | if (cameraView.EnableBarcode)
386 | {
387 | BarcodeQRCodeReader.Result[] results = barcodeReader.DecodeBuffer(bytes, width, height, nPixelStride * nRowStride, BarcodeQRCodeReader.ImagePixelFormat.IPF_GRAYSCALED);
388 | BarcodeResult[] barcodeResults = new BarcodeResult[0];
389 | if (results != null && results.Length > 0)
390 | {
391 | barcodeResults = new BarcodeResult[results.Length];
392 |
393 | for (int i = 0; i < results.Length; i++)
394 | {
395 | barcodeResults[i] = new BarcodeResult()
396 | {
397 | Text = results[i].Text,
398 | Points = results[i].Points,
399 | Format1 = results[i].Format1,
400 | Format2 = results[i].Format2
401 | };
402 | }
403 | }
404 | cameraView.NotifyResultReady(barcodeResults, width, height);
405 | }
406 |
407 | if (cameraView.EnableDocumentDetect)
408 | {
409 | DocumentScanner.Result[] results = documentScanner.DetectBuffer(bytes, width, height, nPixelStride * nRowStride, DocumentScanner.ImagePixelFormat.IPF_GRAYSCALED);
410 | DocumentResult documentResults = new DocumentResult();
411 | if (results != null && results.Length > 0)
412 | {
413 | documentResults = new DocumentResult
414 | {
415 | Confidence = results[0].Confidence,
416 | Points = results[0].Points
417 | };
418 |
419 | if (cameraView.EnableDocumentRectify)
420 | {
421 | NormalizedImage normalizedImage = documentScanner.NormalizeBuffer(bytes, width, height, nPixelStride * nRowStride, DocumentScanner.ImagePixelFormat.IPF_GRAYSCALED, documentResults.Points);
422 | documentResults.Width = normalizedImage.Width;
423 | documentResults.Height = normalizedImage.Height;
424 | documentResults.Stride = normalizedImage.Stride;
425 | documentResults.Format = normalizedImage.Format;
426 | documentResults.Data = normalizedImage.Data;
427 | }
428 | }
429 |
430 | cameraView.NotifyResultReady(documentResults, width, height);
431 |
432 | }
433 |
434 | if (cameraView.EnableMrz)
435 | {
436 | MrzResult mrzResults = new MrzResult();
437 | try
438 | {
439 | MrzScanner.Result[] results = mrzScanner.DetectBuffer(bytes, width, height, nPixelStride * nRowStride, MrzScanner.ImagePixelFormat.IPF_GRAYSCALED);
440 |
441 |
442 | if (results != null && results.Length > 0)
443 | {
444 | Line[] rawData = new Line[results.Length];
445 | string[] lines = new string[results.Length];
446 |
447 | for (int i = 0; i < results.Length; i++)
448 | {
449 | rawData[i] = new Line()
450 | {
451 | Confidence = results[i].Confidence,
452 | Text = results[i].Text,
453 | Points = results[i].Points,
454 | };
455 | lines[i] = results[i].Text;
456 | }
457 |
458 |
459 | Dynamsoft.MrzResult info = MrzParser.Parse(lines);
460 | mrzResults = new MrzResult()
461 | {
462 | RawData = rawData,
463 | Type = info.Type,
464 | Nationality = info.Nationality,
465 | Surname = info.Surname,
466 | GivenName = info.GivenName,
467 | PassportNumber = info.PassportNumber,
468 | IssuingCountry = info.IssuingCountry,
469 | BirthDate = info.BirthDate,
470 | Gender = info.Gender,
471 | Expiration = info.Expiration,
472 | Lines = info.Lines
473 | };
474 |
475 | }
476 | }
477 |
478 | catch (Exception ex)
479 | {
480 | System.Diagnostics.Debug.WriteLine(ex.Message);
481 | }
482 |
483 | cameraView.NotifyResultReady(mrzResults, width, height);
484 | }
485 |
486 | }
487 | catch (Exception ex)
488 | {
489 | System.Diagnostics.Debug.WriteLine(ex.Message);
490 | }
491 | }
492 | }
493 | }
494 |
495 | }
496 |
497 |
--------------------------------------------------------------------------------
/Capture.Vision.Maui.Example/Resources/Styles/Styles.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
10 |
11 |
15 |
16 |
21 |
22 |
25 |
26 |
49 |
50 |
67 |
68 |
88 |
89 |
110 |
111 |
132 |
133 |
138 |
139 |
159 |
160 |
178 |
179 |
183 |
184 |
206 |
207 |
222 |
223 |
243 |
244 |
247 |
248 |
271 |
272 |
292 |
293 |
299 |
300 |
319 |
320 |
323 |
324 |
352 |
353 |
373 |
374 |
378 |
379 |
391 |
392 |
397 |
398 |
404 |
405 |
406 |
--------------------------------------------------------------------------------