diff --git a/.gitignore b/.gitignore
index 740321b79..6572e8060 100644
--- a/.gitignore
+++ b/.gitignore
@@ -39,7 +39,6 @@ TestCloud/
.vs/
project.lock.json
-tools/
.nugetapikey
.mygetapikey
*.xam
diff --git a/Samples/Sample.Android/Sample.Android.csproj b/Samples/Sample.Android/Sample.Android.csproj
index b0c9c7d8b..8454a3062 100644
--- a/Samples/Sample.Android/Sample.Android.csproj
+++ b/Samples/Sample.Android/Sample.Android.csproj
@@ -89,7 +89,6 @@
-
1.3.0.4
diff --git a/Samples/Sample.Forms/Sample.Forms.Android/Sample.Forms.Android.csproj b/Samples/Sample.Forms/Sample.Forms.Android/Sample.Forms.Android.csproj
index dcacc25c9..8d720a563 100644
--- a/Samples/Sample.Forms/Sample.Forms.Android/Sample.Forms.Android.csproj
+++ b/Samples/Sample.Forms/Sample.Forms.Android/Sample.Forms.Android.csproj
@@ -109,4 +109,9 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/Samples/Sample.Forms/Sample.Forms/Sample.Forms.csproj b/Samples/Sample.Forms/Sample.Forms/Sample.Forms.csproj
index 8eaf1848a..19de08fda 100644
--- a/Samples/Sample.Forms/Sample.Forms/Sample.Forms.csproj
+++ b/Samples/Sample.Forms/Sample.Forms/Sample.Forms.csproj
@@ -13,7 +13,7 @@
-
+
diff --git a/Tools/ImageReceiver/ImageReceiver.csproj b/Tools/ImageReceiver/ImageReceiver.csproj
new file mode 100644
index 000000000..c78c9c7e8
--- /dev/null
+++ b/Tools/ImageReceiver/ImageReceiver.csproj
@@ -0,0 +1,9 @@
+
+
+
+ net6.0
+ enable
+ enable
+
+
+
diff --git a/Tools/ImageReceiver/Program.cs b/Tools/ImageReceiver/Program.cs
new file mode 100644
index 000000000..896d25c19
--- /dev/null
+++ b/Tools/ImageReceiver/Program.cs
@@ -0,0 +1,37 @@
+
+var builder = WebApplication.CreateBuilder(args);
+var app = builder.Build();
+
+var picNum = 0;
+
+var imagePath = Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)!, "Images");
+Directory.CreateDirectory(imagePath);
+Console.WriteLine($"Saving images to: {imagePath}");
+
+
+app.MapPost("/", async context =>
+{
+ try
+ {
+ var filePrefix = "File";
+ if (context.Request.Headers.TryGetValue("FileName", out var newFilePrefix))
+ {
+ filePrefix = newFilePrefix;
+ }
+
+ using (var fs = File.Create(Path.Combine(imagePath, $"{filePrefix}_{picNum}.bmp")))
+ {
+ picNum++;
+ await context.Request.Body.CopyToAsync(fs);
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.Error.WriteLine($"Failed to save File_{picNum - 1}.bmp");
+ Console.Error.WriteLine(ex.Message);
+ }
+
+ context.Response.StatusCode = 200;
+});
+
+app.Run();
\ No newline at end of file
diff --git a/Tools/ImageReceiver/Properties/launchSettings.json b/Tools/ImageReceiver/Properties/launchSettings.json
new file mode 100644
index 000000000..76b7d4176
--- /dev/null
+++ b/Tools/ImageReceiver/Properties/launchSettings.json
@@ -0,0 +1,27 @@
+{
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:63208",
+ "sslPort": 0
+ }
+ },
+ "profiles": {
+ "ImageReceiver": {
+ "commandName": "Project",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "applicationUrl": "https://localhost:5390",
+ "dotnetRunMessages": true
+ },
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Tools/ImageReceiver/appsettings.Development.json b/Tools/ImageReceiver/appsettings.Development.json
new file mode 100644
index 000000000..0c208ae91
--- /dev/null
+++ b/Tools/ImageReceiver/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/Tools/ImageReceiver/appsettings.json b/Tools/ImageReceiver/appsettings.json
new file mode 100644
index 000000000..10f68b8c8
--- /dev/null
+++ b/Tools/ImageReceiver/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/ZXing.Net.Mobile.Forms/ZXing.Net.Mobile.Forms.csproj b/ZXing.Net.Mobile.Forms/ZXing.Net.Mobile.Forms.csproj
index 95095e523..374cb7d3e 100644
--- a/ZXing.Net.Mobile.Forms/ZXing.Net.Mobile.Forms.csproj
+++ b/ZXing.Net.Mobile.Forms/ZXing.Net.Mobile.Forms.csproj
@@ -89,7 +89,7 @@
-
+
diff --git a/ZXing.Net.Mobile.sln b/ZXing.Net.Mobile.sln
index b33050cd7..c4914e227 100644
--- a/ZXing.Net.Mobile.sln
+++ b/ZXing.Net.Mobile.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.29806.167
+# Visual Studio Version 17
+VisualStudioVersion = 17.2.32616.157
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ZXing.Net.Mobile", "ZXing.Net.Mobile\ZXing.Net.Mobile.csproj", "{8B7A8AB6-35A4-4C9C-83E1-96BA8BE3C941}"
EndProject
@@ -32,6 +32,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample.Forms.UWP", "Samples
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Forms.Tizen", "Samples\Sample.Forms\Sample.Forms.Tizen\Sample.Forms.Tizen.csproj", "{9CBD2F34-9649-48DE-9B35-0D1291F4E714}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{09527748-5F83-45A9-B1A4-DB3C85D136FB}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageReceiver", "Tools\ImageReceiver\ImageReceiver.csproj", "{47387C60-8776-44DE-A73E-8D6C72D9BC43}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
@@ -686,6 +690,62 @@ Global
{9CBD2F34-9649-48DE-9B35-0D1291F4E714}.Release|x64.Build.0 = Release|Any CPU
{9CBD2F34-9649-48DE-9B35-0D1291F4E714}.Release|x86.ActiveCfg = Release|Any CPU
{9CBD2F34-9649-48DE-9B35-0D1291F4E714}.Release|x86.Build.0 = Release|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Ad-Hoc|ARM64.ActiveCfg = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Ad-Hoc|ARM64.Build.0 = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.AppStore|Any CPU.Build.0 = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.AppStore|ARM.ActiveCfg = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.AppStore|ARM.Build.0 = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.AppStore|ARM64.ActiveCfg = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.AppStore|ARM64.Build.0 = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.AppStore|iPhone.Build.0 = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.AppStore|x64.ActiveCfg = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.AppStore|x64.Build.0 = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.AppStore|x86.ActiveCfg = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.AppStore|x86.Build.0 = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Debug|ARM.Build.0 = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Debug|iPhone.Build.0 = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Debug|x64.Build.0 = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Debug|x86.Build.0 = Debug|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Release|Any CPU.Build.0 = Release|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Release|ARM.ActiveCfg = Release|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Release|ARM.Build.0 = Release|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Release|ARM64.Build.0 = Release|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Release|iPhone.ActiveCfg = Release|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Release|iPhone.Build.0 = Release|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Release|x64.ActiveCfg = Release|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Release|x64.Build.0 = Release|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Release|x86.ActiveCfg = Release|Any CPU
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -699,6 +759,7 @@ Global
{CFF9673E-1188-4646-BCC7-F7601F3A1D1A} = {E0DF8E5D-AF49-43C9-9921-D67268E75964}
{96FBFDD1-F91A-44F6-962A-51E1AC7AAC63} = {E0DF8E5D-AF49-43C9-9921-D67268E75964}
{9CBD2F34-9649-48DE-9B35-0D1291F4E714} = {E0DF8E5D-AF49-43C9-9921-D67268E75964}
+ {47387C60-8776-44DE-A73E-8D6C72D9BC43} = {09527748-5F83-45A9-B1A4-DB3C85D136FB}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {887F72FF-99D0-4882-A73A-A913A0534F0C}
diff --git a/ZXing.Net.Mobile/Android/CameraAccess/CameraAnalyzer.android.cs b/ZXing.Net.Mobile/Android/CameraAccess/CameraAnalyzer.android.cs
index b8939d2b8..75a40dc68 100644
--- a/ZXing.Net.Mobile/Android/CameraAccess/CameraAnalyzer.android.cs
+++ b/ZXing.Net.Mobile/Android/CameraAccess/CameraAnalyzer.android.cs
@@ -1,155 +1,156 @@
using System;
using System.Threading.Tasks;
+using Android.Content;
using Android.Views;
-using ApxLabs.FastAndroidCamera;
+using ZXing.Net.Mobile.Android;
namespace ZXing.Mobile.CameraAccess
{
- public class CameraAnalyzer
- {
- readonly CameraController cameraController;
- readonly CameraEventsListener cameraEventListener;
- Task processingTask;
- DateTime lastPreviewAnalysis = DateTime.UtcNow;
- bool wasScanned;
- readonly IScannerSessionHost scannerHost;
- BarcodeReaderGeneric barcodeReader;
-
- public CameraAnalyzer(SurfaceView surfaceView, IScannerSessionHost scannerHost)
- {
- this.scannerHost = scannerHost;
- cameraEventListener = new CameraEventsListener();
- cameraController = new CameraController(surfaceView, cameraEventListener, scannerHost);
- Torch = new Torch(cameraController, surfaceView.Context);
- }
-
- public Action BarcodeFound;
-
- public Torch Torch { get; }
-
- public bool IsAnalyzing { get; private set; }
-
- public void PauseAnalysis()
- => IsAnalyzing = false;
-
- public void ResumeAnalysis()
- => IsAnalyzing = true;
-
- public void ShutdownCamera()
- {
- IsAnalyzing = false;
- cameraEventListener.OnPreviewFrameReady -= HandleOnPreviewFrameReady;
- cameraController.ShutdownCamera();
- }
-
- public void SetupCamera()
- {
- cameraEventListener.OnPreviewFrameReady += HandleOnPreviewFrameReady;
- cameraController.SetupCamera();
- barcodeReader = scannerHost.ScanningOptions.BuildBarcodeReader();
- }
-
- public void AutoFocus()
- => cameraController.AutoFocus();
-
- public void AutoFocus(int x, int y)
- => cameraController.AutoFocus(x, y);
-
- public void RefreshCamera()
- => cameraController.RefreshCamera();
-
- bool CanAnalyzeFrame
- {
- get
- {
- if (!IsAnalyzing)
- return false;
-
- //Check and see if we're still processing a previous frame
- // todo: check if we can run as many as possible or mby run two analyzers at once (Vision + ZXing)
- if (processingTask != null && !processingTask.IsCompleted)
- return false;
-
- var elapsedTimeMs = (DateTime.UtcNow - lastPreviewAnalysis).TotalMilliseconds;
- if (elapsedTimeMs < scannerHost.ScanningOptions.DelayBetweenAnalyzingFrames)
- return false;
-
- // Delay a minimum between scans
- if (wasScanned && elapsedTimeMs < scannerHost.ScanningOptions.DelayBetweenContinuousScans)
- return false;
-
- return true;
- }
- }
-
- void HandleOnPreviewFrameReady(object sender, FastJavaByteArray fastArray)
- {
- if (!CanAnalyzeFrame)
- return;
-
- wasScanned = false;
- lastPreviewAnalysis = DateTime.UtcNow;
-
- processingTask = Task.Run(() =>
- {
- try
- {
- DecodeFrame(fastArray);
- }
- catch (Exception ex)
- {
- Console.WriteLine(ex);
- }
- }).ContinueWith(task =>
- {
- if (task.IsFaulted)
- Android.Util.Log.Debug(MobileBarcodeScanner.TAG, "DecodeFrame exception occurs");
- }, TaskContinuationOptions.OnlyOnFaulted);
- }
-
- void DecodeFrame(FastJavaByteArray fastArray)
- {
- var resolution = cameraController.CameraResolution;
- var width = resolution.Width;
- var height = resolution.Height;
-
- var rotate = false;
- var newWidth = width;
- var newHeight = height;
-
- // use last value for performance gain
- var cDegrees = cameraController.LastCameraDisplayOrientationDegree;
-
- if (cDegrees == 90 || cDegrees == 270)
- {
- rotate = true;
- newWidth = height;
- newHeight = width;
- }
-
- var start = PerformanceCounter.Start();
-
- LuminanceSource fast = new FastJavaByteArrayYUVLuminanceSource(fastArray, width, height, 0, 0, width, height); // _area.Left, _area.Top, _area.Width, _area.Height);
- if (rotate)
- fast = fast.rotateCounterClockwise();
-
- var result = barcodeReader.Decode(fast);
-
- fastArray.Dispose();
- fastArray = null;
-
- PerformanceCounter.Stop(start,
- "Decode Time: {0} ms (width: " + width + ", height: " + height + ", degrees: " + cDegrees + ", rotate: " +
- rotate + ")");
-
- if (result != null)
- {
- Android.Util.Log.Debug(MobileBarcodeScanner.TAG, "Barcode Found");
-
- wasScanned = true;
- BarcodeFound?.Invoke(result);
- return;
- }
- }
- }
-}
\ No newline at end of file
+ public class CameraAnalyzer
+ {
+ readonly Context context;
+ readonly CameraController cameraController;
+ readonly CameraEventsListener cameraEventListener;
+ readonly DeviceOrientationEventListener orientationEventListener;
+ Task processingTask;
+ DateTime lastPreviewAnalysis = DateTime.UtcNow;
+ bool wasScanned;
+ readonly IScannerSessionHost scannerHost;
+ BarcodeReaderGeneric barcodeReader;
+
+ public CameraAnalyzer(SurfaceView surfaceView, IScannerSessionHost scannerHost)
+ {
+ context = surfaceView.Context;
+ this.scannerHost = scannerHost;
+ cameraEventListener = new CameraEventsListener();
+ orientationEventListener = new DeviceOrientationEventListener(context, Android.Hardware.SensorDelay.Normal);
+ cameraController = new CameraController(surfaceView, cameraEventListener, scannerHost);
+ Torch = new Torch(cameraController, surfaceView.Context);
+ }
+
+ public Action BarcodeFound;
+
+ public Torch Torch { get; }
+
+ public bool IsAnalyzing { get; private set; }
+
+ public void PauseAnalysis()
+ => IsAnalyzing = false;
+
+ public void ResumeAnalysis()
+ => IsAnalyzing = true;
+
+ public void ShutdownCamera()
+ {
+ IsAnalyzing = false;
+ cameraEventListener.OnPreviewFrameReady -= HandleOnPreviewFrameReady;
+ orientationEventListener.Disable();
+ cameraController.ShutdownCamera();
+ }
+
+ public void SetupCamera()
+ {
+ cameraEventListener.OnPreviewFrameReady += HandleOnPreviewFrameReady;
+ if (orientationEventListener.CanDetectOrientation())
+ {
+ orientationEventListener.Enable();
+ }
+
+ barcodeReader = scannerHost.ScanningOptions.BuildBarcodeReader();
+ Android.Util.Log.Debug(MobileBarcodeScanner.TAG, "Created Barcode Reader");
+
+ cameraController.SetupCamera();
+ }
+
+ public void AutoFocus()
+ => cameraController.AutoFocus();
+
+ public void AutoFocus(int x, int y)
+ => cameraController.AutoFocus(x, y);
+
+ public void RefreshCamera()
+ {
+ cameraController.RefreshCamera();
+ }
+
+ bool CanAnalyzeFrame
+ {
+ get
+ {
+ if (!IsAnalyzing)
+ return false;
+
+ //Check and see if we're still processing a previous frame
+ // todo: check if we can run as many as possible or mby run two analyzers at once (Vision + ZXing)
+ if (processingTask != null && !processingTask.IsCompleted)
+ return false;
+
+ var elapsedTimeMs = (DateTime.UtcNow - lastPreviewAnalysis).TotalMilliseconds;
+ if (elapsedTimeMs < scannerHost.ScanningOptions.DelayBetweenAnalyzingFrames)
+ return false;
+
+ // Delay a minimum between scans
+ if (wasScanned && elapsedTimeMs < scannerHost.ScanningOptions.DelayBetweenContinuousScans)
+ return false;
+
+ return true;
+ }
+ }
+
+ void HandleOnPreviewFrameReady(object sender, CapturedImageData data)
+ {
+ if (!CanAnalyzeFrame)
+ return;
+
+ wasScanned = false;
+ lastPreviewAnalysis = DateTime.UtcNow;
+
+ processingTask = Task.Run(() =>
+ {
+ try
+ {
+ DecodeFrame(data);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex);
+ }
+ }).ContinueWith(task =>
+ {
+ if (task.IsFaulted)
+ Android.Util.Log.Debug(MobileBarcodeScanner.TAG, "DecodeFrame exception occurs");
+ }, TaskContinuationOptions.OnlyOnFaulted);
+ }
+
+ void DecodeFrame(CapturedImageData data)
+ {
+ var start = PerformanceCounter.Start();
+ var orientationData = new DeviceOrientationData(context.Resources.Configuration.Orientation, orientationEventListener.Orientation, cameraController.SensorRotation);
+ var source = new PlanarNV21LuminanceSource(data.Matrix, data.Width, data.Height, orientationData, (!barcodeReader.AutoRotate && orientationEventListener.IsEnabled));
+ var initPerformance = PerformanceCounter.Stop(start);
+
+ //DebugHelper.SendNV21toJPEGToEndpoint(source.Matrix, source.Width, source.Height, "https://local.imagereceiver.ip:5390");
+
+ start = PerformanceCounter.Start();
+ var result = barcodeReader.Decode(source);
+ Android.Util.Log.Debug(
+ MobileBarcodeScanner.TAG,
+ "Decode Time: {0} ms (Width: {1}, Height: {2}, Rotations (S/D): {3} / {4}), Source setup: {5} ms",
+ PerformanceCounter.Stop(start).Milliseconds,
+ data.Width,
+ data.Height,
+ orientationData.SensorRotation,
+ orientationData.DeviceOrientation,
+ initPerformance.Milliseconds);
+
+ if (result != null)
+ {
+ Android.Util.Log.Debug(MobileBarcodeScanner.TAG, "Barcode Found");
+
+ wasScanned = true;
+ BarcodeFound?.Invoke(result);
+ return;
+ }
+ }
+ }
+}
diff --git a/ZXing.Net.Mobile/Android/CameraAccess/CameraCaptureStateListener.android.cs b/ZXing.Net.Mobile/Android/CameraAccess/CameraCaptureStateListener.android.cs
new file mode 100644
index 000000000..5fb1b5b9d
--- /dev/null
+++ b/ZXing.Net.Mobile/Android/CameraAccess/CameraCaptureStateListener.android.cs
@@ -0,0 +1,22 @@
+using System;
+using Android.Hardware.Camera2;
+
+namespace ZXing.Mobile.CameraAccess
+{
+ public class CameraCaptureStateListener : CameraCaptureSession.StateCallback
+ {
+ public Action OnConfigureFailedAction;
+
+ public Action OnConfiguredAction;
+
+ public override void OnConfigureFailed(CameraCaptureSession session)
+ {
+ OnConfigureFailedAction?.Invoke(session);
+ }
+
+ public override void OnConfigured(CameraCaptureSession session)
+ {
+ OnConfiguredAction?.Invoke(session);
+ }
+ }
+}
diff --git a/ZXing.Net.Mobile/Android/CameraAccess/CameraController.android.cs b/ZXing.Net.Mobile/Android/CameraAccess/CameraController.android.cs
index 71354dae5..c35b0cce9 100644
--- a/ZXing.Net.Mobile/Android/CameraAccess/CameraController.android.cs
+++ b/ZXing.Net.Mobile/Android/CameraAccess/CameraController.android.cs
@@ -1,435 +1,472 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Threading;
using Android.Content;
using Android.Graphics;
-using Android.Hardware;
+using Android.Hardware.Camera2;
+using Android.Hardware.Camera2.Params;
+using Android.Media;
using Android.OS;
using Android.Runtime;
+using Android.Util;
using Android.Views;
-using ApxLabs.FastAndroidCamera;
-using Camera = Android.Hardware.Camera;
+using Java.Lang;
namespace ZXing.Mobile.CameraAccess
{
- public class CameraController
- {
- readonly Context context;
- readonly ISurfaceHolder holder;
- readonly SurfaceView surfaceView;
- readonly CameraEventsListener cameraEventListener;
- int cameraId;
- IScannerSessionHost scannerHost;
-
- public CameraController(SurfaceView surfaceView, CameraEventsListener cameraEventListener, IScannerSessionHost scannerHost)
- {
- context = surfaceView.Context;
- holder = surfaceView.Holder;
- this.surfaceView = surfaceView;
- this.cameraEventListener = cameraEventListener;
- this.scannerHost = scannerHost;
- }
-
- public Camera Camera { get; private set; }
-
- public CameraResolution CameraResolution { get; private set; }
-
- public int LastCameraDisplayOrientationDegree { get; private set; }
-
- public void RefreshCamera()
- {
- if (holder == null) return;
-
- ApplyCameraSettings();
-
- try
- {
- Camera.SetPreviewDisplay(holder);
- Camera.StartPreview();
- }
- catch (Exception ex)
- {
- Android.Util.Log.Debug(MobileBarcodeScanner.TAG, ex.ToString());
- }
- }
-
- public void SetupCamera()
- {
- if (Camera != null)
- return;
-
- var perf = PerformanceCounter.Start();
- OpenCamera();
- PerformanceCounter.Stop(perf, "Setup Camera took {0}ms");
-
- if (Camera == null) return;
-
- perf = PerformanceCounter.Start();
- ApplyCameraSettings();
-
- try
- {
- Camera.SetPreviewDisplay(holder);
-
-
- var previewParameters = Camera.GetParameters();
- var previewSize = previewParameters.PreviewSize;
- var bitsPerPixel = ImageFormat.GetBitsPerPixel(previewParameters.PreviewFormat);
-
-
- var bufferSize = (previewSize.Width * previewSize.Height * bitsPerPixel) / 8;
- const int NUM_PREVIEW_BUFFERS = 5;
- for (uint i = 0; i < NUM_PREVIEW_BUFFERS; ++i)
- {
- using (var buffer = new FastJavaByteArray(bufferSize))
- Camera.AddCallbackBuffer(buffer);
- }
-
- Camera.StartPreview();
-
- Camera.SetNonMarshalingPreviewCallback(cameraEventListener);
- }
- catch (Exception ex)
- {
- Android.Util.Log.Debug(MobileBarcodeScanner.TAG, ex.ToString());
- return;
- }
- finally
- {
- PerformanceCounter.Stop(perf, "Setup Camera Parameters took {0}ms");
- }
-
- // Docs suggest if Auto or Macro modes, we should invoke AutoFocus at least once
- var currentFocusMode = Camera.GetParameters().FocusMode;
- if (currentFocusMode == Camera.Parameters.FocusModeAuto
- || currentFocusMode == Camera.Parameters.FocusModeMacro)
- AutoFocus();
- }
-
- public void AutoFocus()
- {
- AutoFocus(0, 0, false);
- }
-
- public void AutoFocus(int x, int y)
- {
- // The bounds for focus areas are actually -1000 to 1000
- // So we need to translate the touch coordinates to this scale
- var focusX = x / surfaceView.Width * 2000 - 1000;
- var focusY = y / surfaceView.Height * 2000 - 1000;
-
- // Call the autofocus with our coords
- AutoFocus(focusX, focusY, true);
- }
-
- public void ShutdownCamera()
- {
- if (Camera == null) return;
-
- // camera release logic takes about 0.005 sec so there is no need in async releasing
- var perf = PerformanceCounter.Start();
- try
- {
- try
- {
- Camera.StopPreview();
- Camera.SetNonMarshalingPreviewCallback(null);
-
- //Camera.SetPreviewCallback(null);
-
- Android.Util.Log.Debug(MobileBarcodeScanner.TAG, $"Calling SetPreviewDisplay: null");
- Camera.SetPreviewDisplay(null);
- }
- catch (Exception ex)
- {
- Android.Util.Log.Error(MobileBarcodeScanner.TAG, ex.ToString());
- }
- Camera.Release();
- Camera = null;
- }
- catch (Exception e)
- {
- Android.Util.Log.Error(MobileBarcodeScanner.TAG, e.ToString());
- }
-
- PerformanceCounter.Stop(perf, "Shutdown camera took {0}ms");
- }
-
- void OpenCamera()
- {
- try
- {
- var version = Build.VERSION.SdkInt;
-
- if (version >= BuildVersionCodes.Gingerbread)
- {
- Android.Util.Log.Debug(MobileBarcodeScanner.TAG, "Checking Number of cameras...");
-
- var numCameras = Camera.NumberOfCameras;
- var camInfo = new Camera.CameraInfo();
- var found = false;
- Android.Util.Log.Debug(MobileBarcodeScanner.TAG, "Found " + numCameras + " cameras...");
-
- var whichCamera = CameraFacing.Back;
-
- if (scannerHost.ScanningOptions.UseFrontCameraIfAvailable.HasValue &&
- scannerHost.ScanningOptions.UseFrontCameraIfAvailable.Value)
- whichCamera = CameraFacing.Front;
-
- for (var i = 0; i < numCameras; i++)
- {
- Camera.GetCameraInfo(i, camInfo);
- if (camInfo.Facing == whichCamera)
- {
- Android.Util.Log.Debug(MobileBarcodeScanner.TAG,
- "Found " + whichCamera + " Camera, opening...");
- Camera = Camera.Open(i);
- cameraId = i;
- found = true;
- break;
- }
- }
-
- if (!found)
- {
- Android.Util.Log.Debug(MobileBarcodeScanner.TAG,
- "Finding " + whichCamera + " camera failed, opening camera 0...");
- Camera = Camera.Open(0);
- cameraId = 0;
- }
- }
- else
- {
- Camera = Camera.Open();
- }
-
- //if (Camera != null)
- // Camera.SetPreviewCallback(_cameraEventListener);
- //else
- // MobileBarcodeScanner.LogWarn(MobileBarcodeScanner.TAG, "Camera is null :(");
- }
- catch (Exception ex)
- {
- ShutdownCamera();
- MobileBarcodeScanner.LogError("Setup Error: {0}", ex);
- }
- }
-
- void ApplyCameraSettings()
- {
- if (Camera == null)
- {
- OpenCamera();
- }
-
- // do nothing if something wrong with camera
- if (Camera == null) return;
-
- var parameters = Camera.GetParameters();
- parameters.PreviewFormat = ImageFormatType.Nv21;
-
- var supportedFocusModes = parameters.SupportedFocusModes;
- if (scannerHost.ScanningOptions.DisableAutofocus)
- parameters.FocusMode = Camera.Parameters.FocusModeFixed;
- else if (Build.VERSION.SdkInt >= BuildVersionCodes.IceCreamSandwich &&
- supportedFocusModes.Contains(Camera.Parameters.FocusModeContinuousPicture))
- parameters.FocusMode = Camera.Parameters.FocusModeContinuousPicture;
- else if (supportedFocusModes.Contains(Camera.Parameters.FocusModeContinuousVideo))
- parameters.FocusMode = Camera.Parameters.FocusModeContinuousVideo;
- else if (supportedFocusModes.Contains(Camera.Parameters.FocusModeAuto))
- parameters.FocusMode = Camera.Parameters.FocusModeAuto;
- else if (supportedFocusModes.Contains(Camera.Parameters.FocusModeFixed))
- parameters.FocusMode = Camera.Parameters.FocusModeFixed;
-
- var selectedFps = parameters.SupportedPreviewFpsRange.FirstOrDefault();
- if (selectedFps != null)
- {
- // This will make sure we select a range with the highest maximum fps
- // which still has the lowest minimum fps (Widest Range)
- foreach (var fpsRange in parameters.SupportedPreviewFpsRange)
- {
- if (fpsRange[1] > selectedFps[1] || fpsRange[1] == selectedFps[1] && fpsRange[0] < selectedFps[0])
- selectedFps = fpsRange;
- }
- parameters.SetPreviewFpsRange(selectedFps[0], selectedFps[1]);
- }
-
- CameraResolution resolution = null;
- var supportedPreviewSizes = parameters.SupportedPreviewSizes;
- if (supportedPreviewSizes != null)
- {
- var availableResolutions = supportedPreviewSizes.Select(sps => new CameraResolution
- {
- Width = sps.Width,
- Height = sps.Height
- });
-
- // Try and get a desired resolution from the options selector
- resolution = scannerHost.ScanningOptions.GetResolution(availableResolutions.ToList());
-
- // If the user did not specify a resolution, let's try and find a suitable one
- if (resolution == null)
- {
- foreach (var sps in supportedPreviewSizes)
- {
- if (sps.Width >= 640 && sps.Width <= 1000 && sps.Height >= 360 && sps.Height <= 1000)
- {
- resolution = new CameraResolution
- {
- Width = sps.Width,
- Height = sps.Height
- };
- break;
- }
- }
- }
- }
-
- // Google Glass requires this fix to display the camera output correctly
- if (Build.Model.Contains("Glass"))
- {
- resolution = new CameraResolution
- {
- Width = 640,
- Height = 360
- };
- // Glass requires 30fps
- parameters.SetPreviewFpsRange(30000, 30000);
- }
-
- // Hopefully a resolution was selected at some point
- if (resolution != null)
- {
- Android.Util.Log.Debug(MobileBarcodeScanner.TAG,
- "Selected Resolution: " + resolution.Width + "x" + resolution.Height);
-
- CameraResolution = resolution;
- parameters.SetPreviewSize(resolution.Width, resolution.Height);
- }
-
- Camera.SetParameters(parameters);
-
- SetCameraDisplayOrientation();
- }
-
- void AutoFocus(int x, int y, bool useCoordinates)
- {
- if (Camera == null) return;
-
- if (scannerHost.ScanningOptions.DisableAutofocus)
- {
- Android.Util.Log.Debug(MobileBarcodeScanner.TAG, "AutoFocus Disabled");
- return;
- }
-
- var cameraParams = Camera.GetParameters();
-
- Android.Util.Log.Debug(MobileBarcodeScanner.TAG, "AutoFocus Requested");
-
- // Cancel any previous requests
- Camera.CancelAutoFocus();
-
- try
- {
- // If we want to use coordinates
- // Also only if our camera supports Auto focus mode
- // Since FocusAreas only really work with FocusModeAuto set
- if (useCoordinates
- && cameraParams.SupportedFocusModes.Contains(Camera.Parameters.FocusModeAuto))
- {
- // Let's give the touched area a 20 x 20 minimum size rect to focus on
- // So we'll offset -10 from the center of the touch and then
- // make a rect of 20 to give an area to focus on based on the center of the touch
- x = x - 10;
- y = y - 10;
-
- // Ensure we don't go over the -1000 to 1000 limit of focus area
- if (x >= 1000)
- x = 980;
- if (x < -1000)
- x = -1000;
- if (y >= 1000)
- y = 980;
- if (y < -1000)
- y = -1000;
-
- // Explicitly set FocusModeAuto since Focus areas only work with this setting
- cameraParams.FocusMode = Camera.Parameters.FocusModeAuto;
- // Add our focus area
- cameraParams.FocusAreas = new List
- {
- new Camera.Area(new Rect(x, y, x + 20, y + 20), 1000)
- };
- Camera.SetParameters(cameraParams);
- }
-
- // Finally autofocus (weather we used focus areas or not)
- Camera.AutoFocus(cameraEventListener);
- }
- catch (Exception ex)
- {
- Android.Util.Log.Debug(MobileBarcodeScanner.TAG, "AutoFocus Failed: {0}", ex);
- }
- }
-
- void SetCameraDisplayOrientation()
- {
- var degrees = GetCameraDisplayOrientation();
- LastCameraDisplayOrientationDegree = degrees;
-
- Android.Util.Log.Debug(MobileBarcodeScanner.TAG, "Changing Camera Orientation to: " + degrees);
-
- try
- {
- Camera.SetDisplayOrientation(degrees);
- }
- catch (Exception ex)
- {
- Android.Util.Log.Error(MobileBarcodeScanner.TAG, ex.ToString());
- }
- }
-
- int GetCameraDisplayOrientation()
- {
- int degrees;
- var windowManager = context.GetSystemService(Context.WindowService).JavaCast();
- var display = windowManager.DefaultDisplay;
- var rotation = display.Rotation;
-
- switch (rotation)
- {
- case SurfaceOrientation.Rotation0:
- degrees = 0;
- break;
- case SurfaceOrientation.Rotation90:
- degrees = 90;
- break;
- case SurfaceOrientation.Rotation180:
- degrees = 180;
- break;
- case SurfaceOrientation.Rotation270:
- degrees = 270;
- break;
- default:
- throw new ArgumentOutOfRangeException();
- }
-
- var info = new Camera.CameraInfo();
- Camera.GetCameraInfo(cameraId, info);
-
- int correctedDegrees;
- if (info.Facing == CameraFacing.Front)
- {
- correctedDegrees = (info.Orientation + degrees) % 360;
- correctedDegrees = (360 - correctedDegrees) % 360; // compensate the mirror
- }
- else
- {
- // back-facing
- correctedDegrees = (info.Orientation - degrees + 360) % 360;
- }
-
- return correctedDegrees;
- }
- }
-}
\ No newline at end of file
+ public class CameraController
+ {
+ readonly Context context;
+ readonly ISurfaceHolder holder;
+ readonly SurfaceView surfaceView;
+ readonly CameraEventsListener cameraEventListener;
+ readonly IScannerSessionHost scannerHost;
+ readonly CameraStateCallback cameraStateCallback;
+
+ CameraManager cameraManager;
+ ImageReader imageReader;
+ bool flashSupported;
+ Handler backgroundHandler;
+ CaptureRequest.Builder previewBuilder;
+ CameraCaptureSession previewSession;
+ CaptureRequest previewRequest;
+ HandlerThread backgroundThread;
+ Size[] supportedSizes;
+
+ public string CameraId { get; private set; }
+
+ public bool OpeningCamera { get; private set; }
+
+ public CameraDevice Camera { get; private set; }
+
+ public Size DisplaySize { get; private set; }
+
+ public Size IdealPhotoSize { get; private set; }
+
+ public int SensorRotation { get; private set; }
+
+ public CameraController(SurfaceView surfaceView, CameraEventsListener cameraEventListener, IScannerSessionHost scannerHost)
+ {
+ context = surfaceView.Context;
+ holder = surfaceView.Holder;
+ this.surfaceView = surfaceView;
+ this.cameraEventListener = cameraEventListener;
+ this.scannerHost = scannerHost;
+ cameraStateCallback = new CameraStateCallback()
+ {
+ OnErrorAction = (camera, error) =>
+ {
+ camera.Close();
+
+ Camera = null;
+ OpeningCamera = false;
+
+ Android.Util.Log.Debug(MobileBarcodeScanner.TAG, "Error on opening camera: " + error);
+ },
+ OnOpenedAction = camera =>
+ {
+ Camera = camera;
+ StartPreview();
+ AutoFocus();
+ OpeningCamera = false;
+ },
+ OnDisconnectedAction = camera =>
+ {
+ camera.Close();
+ Camera = null;
+ OpeningCamera = false;
+ }
+ };
+ }
+
+ public void RefreshCamera()
+ {
+ if (Camera is null || previewRequest is null || previewSession is null || previewBuilder is null) return;
+
+ SetUpCameraOutputs();
+ previewRequest.Dispose();
+ previewSession.Dispose();
+ previewBuilder.Dispose();
+ StartPreview();
+ }
+
+ public void SetupCamera()
+ {
+ StartBackgroundThread();
+
+ OpenCamera();
+ }
+
+ public void ShutdownCamera()
+ {
+ if (Camera != null)
+ Camera.Close();
+
+ StopBackgroundThread();
+ }
+
+ public void AutoFocus()
+ {
+ AutoFocus(0, 0, false);
+ }
+
+ public void AutoFocus(int x, int y)
+ {
+ // Call the autofocus with our coords
+ AutoFocus(x, y, true);
+ }
+
+ void AutoFocus(int x, int y, bool useCoordinates)
+ {
+ if (Camera == null) return;
+
+ try
+ {
+ if (scannerHost.ScanningOptions.DisableAutofocus)
+ {
+ Log.Debug(MobileBarcodeScanner.TAG, "AutoFocus Disabled");
+ return;
+ }
+
+ var characteristics = cameraManager.GetCameraCharacteristics(CameraId.ToString());
+ var map = (StreamConfigurationMap)characteristics.Get(CameraCharacteristics.ScalerStreamConfigurationMap);
+ var supportedFocusModes = ((int[])characteristics
+ .Get(CameraCharacteristics.ControlAfAvailableModes))
+ .Select(x => (ControlAFMode)x);
+
+ Log.Debug(MobileBarcodeScanner.TAG, "AutoFocus Requested");
+
+ // If we want to use coordinates
+ // Also only if our camera supports Auto focus mode
+ // Since FocusAreas only really work with FocusModeAuto set
+ if (supportedFocusModes.Contains(ControlAFMode.ContinuousVideo))
+ {
+ previewBuilder.Set(CaptureRequest.ControlAfTrigger, (int)ControlAFTrigger.Cancel);
+ previewBuilder.Set(CaptureRequest.ControlAfMode, (int)ControlAFMode.ContinuousVideo);
+ }
+ else if (useCoordinates && supportedFocusModes.Contains(ControlAFMode.Auto))
+ {
+ // Let's give the touched area a 20 x 20 minimum size rect to focus on
+ // So we'll offset -10 from the center of the touch and then
+ // make a rect of 20 to give an area to focus on based on the center of the touch
+ x = x - 10;
+ y = y - 10;
+
+ // Explicitly set FocusModeAuto since Focus areas only work with this setting
+ previewBuilder.Set(CaptureRequest.ControlAfMode, (int)ControlAFMode.Auto);
+ // Add our focus area
+ previewBuilder.Set(CaptureRequest.ControlAfRegions, new MeteringRectangle[]
+ {
+ new MeteringRectangle(x, y, x + 20, y + 20, 1000)
+ });
+
+ previewBuilder.Set(CaptureRequest.ControlAeRegions, new MeteringRectangle[]
+ {
+ new MeteringRectangle(x, y, x + 20, y + 20, 1000)
+ });
+
+ // Finally autofocus (weather we used focus areas or not)
+ previewBuilder.Set(CaptureRequest.ControlAfTrigger, (int)ControlAFTrigger.Start);
+ }
+
+ UpdatePreview();
+ }
+ catch (System.Exception ex)
+ {
+ Log.Debug(MobileBarcodeScanner.TAG, "AutoFocus Failed: {0}", ex);
+ }
+ }
+
+ void SetUpCameraOutputs()
+ {
+ try
+ {
+ cameraManager = (CameraManager)context.GetSystemService(Context.CameraService);
+
+ var cameraIds = cameraManager.GetCameraIdList();
+
+ CameraId = cameraIds[0];
+
+ var whichCamera = LensFacing.Back;
+
+ if (scannerHost.ScanningOptions.UseFrontCameraIfAvailable.HasValue &&
+ scannerHost.ScanningOptions.UseFrontCameraIfAvailable.Value)
+ whichCamera = LensFacing.Front;
+
+ for (var i = 0; i < cameraIds.Length; i++)
+ {
+ var cameraCharacteristics = cameraManager.GetCameraCharacteristics(cameraIds[i]);
+
+ var facing = (Integer)cameraCharacteristics.Get(CameraCharacteristics.LensFacing);
+ if (facing != null && facing.IntValue() == (int)whichCamera)
+ {
+ CameraId = cameraIds[i];
+ break;
+ }
+ }
+
+ var characteristics = cameraManager.GetCameraCharacteristics(CameraId);
+ var map = (StreamConfigurationMap)characteristics.Get(CameraCharacteristics.ScalerStreamConfigurationMap);
+
+ if (characteristics != null && supportedSizes == null)
+ supportedSizes = ((StreamConfigurationMap)characteristics
+ .Get(CameraCharacteristics.ScalerStreamConfigurationMap))
+ .GetOutputSizes((int)ImageFormatType.Yuv420888);
+
+ if (supportedSizes is null || supportedSizes.Length == 0)
+ {
+ Log.Debug(MobileBarcodeScanner.TAG, "Failed to get supported output sizes");
+ return;
+ }
+
+ var wm = context.GetSystemService(Context.WindowService).JavaCast();
+ var display = wm.DefaultDisplay;
+ var point = new Point();
+ display.GetSize(point);
+ DisplaySize = new Size(point.X, point.Y);
+
+ IdealPhotoSize = DisplaySize.Width > DisplaySize.Height ? GetOptimalSize(supportedSizes, DisplaySize) : GetOptimalSize(supportedSizes, DisplaySize, true);
+
+ imageReader = ImageReader.NewInstance(IdealPhotoSize.Width, IdealPhotoSize.Height, ImageFormatType.Yuv420888, 8);
+
+ flashSupported = HasFlash(characteristics);
+ SensorRotation = GetSensorRotation(characteristics);
+
+ imageReader.SetOnImageAvailableListener(cameraEventListener, backgroundHandler);
+
+ }
+ catch (System.Exception ex)
+ {
+ Log.Debug(MobileBarcodeScanner.TAG, "Could not setup camera outputs" + ex);
+ }
+ }
+
+ bool HasFlash(CameraCharacteristics characteristics)
+ {
+ var available = (Java.Lang.Boolean)characteristics.Get(CameraCharacteristics.FlashInfoAvailable);
+ if (available == null)
+ {
+ return false;
+ }
+ else
+ {
+ return (bool)available;
+ }
+ }
+
+ int GetSensorRotation(CameraCharacteristics characteristics)
+ {
+ var rotation = (int?)characteristics.Get(CameraCharacteristics.SensorOrientation);
+ if (rotation == null)
+ {
+ return 0;
+ }
+ else
+ {
+
+ return rotation.Value;
+ }
+ }
+
+ public void OpenCamera()
+ {
+ if (context == null || OpeningCamera)
+ {
+ return;
+ }
+
+ try
+ {
+ OpeningCamera = true;
+
+ SetUpCameraOutputs();
+
+ cameraManager.OpenCamera(CameraId, cameraStateCallback, backgroundHandler);
+ }
+ catch (System.Exception ex)
+ {
+ Log.Debug(MobileBarcodeScanner.TAG, "Error on opening camera" + ex);
+ }
+ }
+
+ Size GetOptimalPreviewSize(SurfaceView surface)
+ {
+ var width = surface.Width > surface.Height ? surface.Width : surface.Height;
+ var height = surface.Width > surface.Height ? surface.Height : surface.Width;
+ var aspectRatio = (double)width / (double)height;
+
+ var characteristics = cameraManager.GetCameraCharacteristics(CameraId);
+ var map = (StreamConfigurationMap)characteristics.Get(CameraCharacteristics.ScalerStreamConfigurationMap);
+ var availableSizes = ((StreamConfigurationMap)characteristics
+ .Get(CameraCharacteristics.ScalerStreamConfigurationMap))
+ .GetOutputSizes(Class.FromType(typeof(ISurfaceHolder)));
+ var availableAspectRatios = availableSizes.Select(x => (x, (double)x.Width / (double)x.Height));
+
+ var differences = availableAspectRatios.Select(x => (x.x, System.Math.Abs(x.Item2 - aspectRatio)));
+ var bestMatches = differences.OrderBy(x => x.Item2).ThenBy(x => System.Math.Abs(x.x.Width - width)).ThenBy(x => System.Math.Abs(x.x.Height - height)).Take(availableSizes.Count() / 2);
+ var matches = bestMatches.OrderBy(m => m.Item2).ThenByDescending(x => x.x.Width).ThenByDescending(x => x.x.Height);
+ return matches.First().x;
+ }
+
+ void SetupHolderSize()
+ {
+ if (Camera is null || holder is null || imageReader is null || backgroundHandler is null) return;
+
+ var optimalPreviewSize = GetOptimalPreviewSize(surfaceView);
+ if (Looper.MyLooper() == Looper.MainLooper)
+ {
+ holder.SetFixedSize(optimalPreviewSize.Width, optimalPreviewSize.Height);
+ }
+ else
+ {
+ var sizeSetResetEvent = new ManualResetEventSlim(false);
+ using (var handler = new Handler(Looper.MainLooper))
+ {
+ handler.Post(() =>
+ {
+ holder.SetFixedSize(optimalPreviewSize.Width, optimalPreviewSize.Height);
+ sizeSetResetEvent.Set();
+ });
+ }
+
+ sizeSetResetEvent.Wait();
+ sizeSetResetEvent.Reset();
+ }
+ }
+
+ public void StartPreview()
+ {
+ if (Camera is null || holder is null || imageReader is null || backgroundHandler is null) return;
+
+ try
+ {
+ SetupHolderSize();
+
+ // This is needed bc otherwise the preview is sometimes distorted
+ System.Threading.Thread.Sleep(30);
+
+ var surfaces = new List
+ {
+ holder.Surface,
+ imageReader.Surface
+ };
+
+ previewBuilder = Camera.CreateCaptureRequest(CameraTemplate.Preview);
+ foreach (var surface in surfaces)
+ {
+ previewBuilder.AddTarget(surface);
+ }
+
+ Camera.CreateCaptureSession(surfaces,
+ new CameraCaptureStateListener
+ {
+ OnConfigureFailedAction = session =>
+ {
+ },
+ OnConfiguredAction = session =>
+ {
+ if (previewSession != null)
+ previewSession.Dispose();
+
+ previewSession = session;
+ UpdatePreview();
+ }
+ },
+ backgroundHandler);
+ }
+ catch (System.Exception ex)
+ {
+ Log.Debug(MobileBarcodeScanner.TAG, "Error on starting preview" + ex);
+ }
+ }
+
+ void UpdatePreview()
+ {
+ if (Camera is null || previewSession is null) return;
+
+ try
+ {
+ SetupHolderSize();
+
+ if (previewRequest != null)
+ previewRequest.Dispose();
+
+ previewRequest = previewBuilder.Build();
+ previewSession.SetRepeatingRequest(previewRequest, null, backgroundHandler);
+ }
+ catch (System.Exception ex)
+ {
+ Log.Debug(MobileBarcodeScanner.TAG, "Error on updating preview" + ex);
+ }
+ }
+
+ Size GetOptimalSize(IList sizes, Size referenceSize, bool flipWidthWithHeight = false) => flipWidthWithHeight ? GetOptimalSize(sizes, referenceSize.Height, referenceSize.Width) : GetOptimalSize(sizes, referenceSize.Width, referenceSize.Height);
+
+ Size GetOptimalSize(IList sizes, int width, int height)
+ {
+ const int minimumSize = 500;
+ const int maximumSize = 1280;
+ if (sizes is null) return null;
+
+ var aspectRatio = (double)width / (double)height;
+ var availableAspectRatios = sizes.Select(x => (x, (double)x.Width / (double)x.Height));
+
+ var differences = availableAspectRatios.Select(x => (x.x, System.Math.Abs(x.Item2 - aspectRatio)));
+ var bestMatches = differences.OrderBy(x => x.Item2).ThenBy(x => System.Math.Abs(x.x.Width - width)).ThenBy(x => System.Math.Abs(x.x.Height - height)).Take(sizes.Count / 2);
+ var orderedMatches = bestMatches.OrderBy(x => x.x.Width).ThenBy(x => x.x.Height);
+ var matches = orderedMatches.Where(m => (m.x.Height >= minimumSize || m.x.Width >= minimumSize) && (m.x.Height <= maximumSize || m.x.Width <= maximumSize));
+
+ if (matches.Count() == 0)
+ {
+ matches = orderedMatches;
+ }
+
+ return matches.First().x;
+ }
+
+ void StartBackgroundThread()
+ {
+ backgroundThread = new HandlerThread("CameraBackgroundThread");
+ backgroundThread.Start();
+ backgroundHandler = new Handler(backgroundThread.Looper);
+ }
+
+ public void EnableTorch(bool state)
+ {
+ try
+ {
+ if (!flashSupported || previewBuilder is null) return;
+
+ if (state)
+ {
+ previewBuilder.Set(CaptureRequest.ControlAeMode, (int)ControlAEMode.On);
+ previewBuilder.Set(CaptureRequest.FlashMode, (int)FlashMode.Torch);
+ }
+ else
+ previewBuilder.Set(CaptureRequest.FlashMode, (int)FlashMode.Off);
+
+ UpdatePreview();
+ }
+ catch (System.Exception ex)
+ {
+ Log.Debug(MobileBarcodeScanner.TAG, "Error on enabling torch" + ex);
+ }
+ }
+
+ void StopBackgroundThread()
+ {
+ try
+ {
+ backgroundThread?.QuitSafely();
+ backgroundThread?.Join();
+ backgroundThread = null;
+ backgroundHandler = null;
+ }
+ catch (InterruptedException ex)
+ {
+ Log.Debug(MobileBarcodeScanner.TAG, "Error stopping background threads: " + ex);
+ }
+ }
+ }
+}
diff --git a/ZXing.Net.Mobile/Android/CameraAccess/CameraEventsListener.android.cs b/ZXing.Net.Mobile/Android/CameraAccess/CameraEventsListener.android.cs
index e42eaf560..d32e58db8 100644
--- a/ZXing.Net.Mobile/Android/CameraAccess/CameraEventsListener.android.cs
+++ b/ZXing.Net.Mobile/Android/CameraAccess/CameraEventsListener.android.cs
@@ -1,29 +1,120 @@
using System;
-using Android.Hardware;
-using ApxLabs.FastAndroidCamera;
+using Android.Media;
+using Java.Nio;
+using static Android.Media.ImageReader;
namespace ZXing.Mobile.CameraAccess
{
- public class CameraEventsListener : Java.Lang.Object, INonMarshalingPreviewCallback, Camera.IAutoFocusCallback
- {
- public event EventHandler OnPreviewFrameReady;
-
- public void OnPreviewFrame(IntPtr data, Camera camera)
- {
- if (data != null && data != IntPtr.Zero)
- {
- using (var fastArray = new FastJavaByteArray(data))
- {
- OnPreviewFrameReady?.Invoke(this, fastArray);
-
- camera.AddCallbackBuffer(fastArray);
- }
- }
- }
-
- public void OnAutoFocus(bool success, Camera camera)
- {
- Android.Util.Log.Debug(MobileBarcodeScanner.TAG, "AutoFocus {0}", success ? "Succeeded" : "Failed");
- }
- }
+ public class CameraEventsListener : Java.Lang.Object, IOnImageAvailableListener
+ {
+ public event EventHandler OnPreviewFrameReady;
+
+ public CameraEventsListener()
+ {
+ }
+
+ public void OnImageAvailable(ImageReader reader)
+ {
+ Image image = null;
+ try
+ {
+ image = reader.AcquireLatestImage();
+
+ if (image is null) return;
+
+ var bytes = Yuv420888toNv21(image);
+ OnPreviewFrameReady?.Invoke(null, new CapturedImageData(bytes, image.Width, image.Height));
+ }
+ finally
+ {
+ image?.Close();
+ }
+ }
+
+ //https://stackoverflow.com/questions/52726002/camera2-captured-picture-conversion-from-yuv-420-888-to-nv21
+ byte[] Yuv420888toNv21(Image image)
+ {
+ var width = image.Width;
+ var height = image.Height;
+ var ySize = width * height;
+ var uvSize = width * height / 4;
+
+ var nv21 = new byte[ySize + uvSize * 2];
+ var planes = image.GetPlanes();
+
+ var yBuffer = planes[0].Buffer; // Y
+ var uBuffer = planes[1].Buffer; // U
+ var vBuffer = planes[2].Buffer; // V
+
+ var yArray = new byte[yBuffer.Limit()];
+ yBuffer.Get(yArray, 0, yArray.Length);
+
+ var uArray = new byte[uBuffer.Limit()];
+ uBuffer.Get(uArray, 0, uArray.Length);
+
+ var vArray = new byte[vBuffer.Limit()];
+ vBuffer.Get(vArray, 0, vArray.Length);
+
+ var rowStride = planes[0].RowStride;
+ var pos = 0;
+
+ if (rowStride == width)
+ {
+ // likely
+ Array.Copy(yArray, 0, nv21, 0, ySize);
+ pos += ySize;
+ }
+ else
+ {
+ var yBufferPos = -rowStride; // not an actual position
+ for (; pos < ySize; pos += width)
+ {
+ yBufferPos += rowStride;
+ Array.Copy(yArray, yBufferPos, nv21, pos, width);
+ }
+ }
+
+ rowStride = planes[2].RowStride;
+ var pixelStride = planes[2].PixelStride;
+
+ if (pixelStride == 2 && rowStride == width && uArray[0] == vArray[1])
+ {
+ // maybe V and U planes overlap as per NV21, which means vBuffer[1] is alias of uBuffer[0]
+ var savePixel = vArray[1];
+ try
+ {
+ vArray[1] = (byte)~savePixel;
+ if (uArray[0] == (sbyte)~savePixel)
+ {
+ vArray[1] = savePixel;
+ Array.Copy(vArray, 0, nv21, ySize, 1);
+ Array.Copy(vArray, 0, nv21, ySize + 1, uArray.Length);
+
+ return nv21; // shortcut
+ }
+ }
+ catch (ReadOnlyBufferException)
+ {
+ // unfortunately, we cannot check if vBuffer and uBuffer overlap
+ }
+
+ // unfortunately, the check failed. We must save U and V pixel by pixel
+ vArray[1] = savePixel;
+ }
+
+ // other optimizations could check if (pixelStride == 1) or (pixelStride == 2),
+ // but performance gain would be less significant
+ for (var row = 0; row < height / 2; row++)
+ {
+ for (var col = 0; col < width / 2; col++)
+ {
+ var vuPos = col * pixelStride + row * rowStride;
+ nv21[pos++] = vArray[vuPos];
+ nv21[pos++] = uArray[vuPos];
+ }
+ }
+
+ return nv21;
+ }
+ }
}
diff --git a/ZXing.Net.Mobile/Android/CameraAccess/CameraStateCallback.android.cs b/ZXing.Net.Mobile/Android/CameraAccess/CameraStateCallback.android.cs
new file mode 100644
index 000000000..804553d2f
--- /dev/null
+++ b/ZXing.Net.Mobile/Android/CameraAccess/CameraStateCallback.android.cs
@@ -0,0 +1,22 @@
+using System;
+using Android.Hardware.Camera2;
+using Android.Runtime;
+
+namespace ZXing.Mobile.CameraAccess
+{
+ public class CameraStateCallback : CameraDevice.StateCallback
+ {
+ public Action OnDisconnectedAction;
+ public Action OnErrorAction;
+ public Action OnOpenedAction;
+
+ public override void OnDisconnected(CameraDevice camera)
+ => OnDisconnectedAction?.Invoke(camera);
+
+ public override void OnError(CameraDevice camera, [GeneratedEnum] CameraError error)
+ => OnErrorAction?.Invoke(camera, error);
+
+ public override void OnOpened(CameraDevice camera)
+ => OnOpenedAction?.Invoke(camera);
+ }
+}
diff --git a/ZXing.Net.Mobile/Android/CameraAccess/CapturedImageData.android.cs b/ZXing.Net.Mobile/Android/CameraAccess/CapturedImageData.android.cs
new file mode 100644
index 000000000..b7ac0e3f8
--- /dev/null
+++ b/ZXing.Net.Mobile/Android/CameraAccess/CapturedImageData.android.cs
@@ -0,0 +1,18 @@
+namespace ZXing.Mobile.CameraAccess
+{
+ public class CapturedImageData
+ {
+ public byte[] Matrix { get; private set; }
+
+ public int Width { get; private set; }
+
+ public int Height { get; private set; }
+
+ public CapturedImageData(byte[] matrix, int width, int height)
+ {
+ Matrix = matrix;
+ Width = width;
+ Height = height;
+ }
+ }
+}
diff --git a/ZXing.Net.Mobile/Android/CameraAccess/Torch.android.cs b/ZXing.Net.Mobile/Android/CameraAccess/Torch.android.cs
index faacb42f5..5f4b5c431 100644
--- a/ZXing.Net.Mobile/Android/CameraAccess/Torch.android.cs
+++ b/ZXing.Net.Mobile/Android/CameraAccess/Torch.android.cs
@@ -1,94 +1,75 @@
using Android.Content;
using Android.Content.PM;
using Android.Hardware;
+using Android.Hardware.Camera2;
+using Android.Hardware.Camera2.Params;
namespace ZXing.Mobile.CameraAccess
{
- public class Torch
- {
- readonly CameraController cameraController;
- readonly Context context;
- bool? hasTorch;
-
- public Torch(CameraController cameraController, Context context)
- {
- this.cameraController = cameraController;
- this.context = context;
- }
-
- public bool IsSupported
- {
- get
- {
- if (hasTorch.HasValue)
- return hasTorch.Value;
-
- if (!context.PackageManager.HasSystemFeature(PackageManager.FeatureCameraFlash))
- {
- Android.Util.Log.Info(MobileBarcodeScanner.TAG, "Flash not supported on this device");
- return false;
- }
-
- if (cameraController.Camera == null)
- {
- Android.Util.Log.Info(MobileBarcodeScanner.TAG, "Run camera first");
- return false;
- }
-
- var p = cameraController.Camera.GetParameters();
- var supportedFlashModes = p.SupportedFlashModes;
-
- if ((supportedFlashModes != null)
- && (supportedFlashModes.Contains(Camera.Parameters.FlashModeTorch)
- || supportedFlashModes.Contains(Camera.Parameters.FlashModeOn)))
- hasTorch = ZXing.Net.Mobile.Android.PermissionsHandler.IsTorchPermissionDeclared();
-
- return hasTorch != null && hasTorch.Value;
- }
- }
-
- public bool IsEnabled { get; private set; }
-
- public void TurnOn() => Enable(true);
-
- public void TurnOff() => Enable(false);
-
- public void Toggle() => Enable(!IsEnabled);
-
- private void Enable(bool state)
- {
- if (!IsSupported || IsEnabled == state)
- return;
-
- if (cameraController.Camera == null)
- {
- Android.Util.Log.Info(MobileBarcodeScanner.TAG, "NULL Camera, cannot toggle torch");
- return;
- }
-
- var parameters = cameraController.Camera.GetParameters();
- var supportedFlashModes = parameters.SupportedFlashModes;
-
- var flashMode = string.Empty;
- if (state)
- {
- if (supportedFlashModes.Contains(Camera.Parameters.FlashModeTorch))
- flashMode = Camera.Parameters.FlashModeTorch;
- else if (supportedFlashModes.Contains(Camera.Parameters.FlashModeOn))
- flashMode = Camera.Parameters.FlashModeOn;
- }
- else
- {
- if (supportedFlashModes != null && supportedFlashModes.Contains(Camera.Parameters.FlashModeOff))
- flashMode = Camera.Parameters.FlashModeOff;
- }
-
- if (!string.IsNullOrEmpty(flashMode))
- {
- parameters.FlashMode = flashMode;
- cameraController.Camera.SetParameters(parameters);
- IsEnabled = state;
- }
- }
- }
-}
\ No newline at end of file
+ public class Torch
+ {
+ readonly CameraController cameraController;
+ readonly Context context;
+ CameraManager cameraManager;
+ bool? hasTorch;
+
+ public Torch(CameraController cameraController, Context context)
+ {
+ this.cameraController = cameraController;
+ this.context = context;
+ cameraManager = (CameraManager)context.GetSystemService(Context.CameraService);
+ }
+
+ public bool IsSupported
+ {
+ get
+ {
+ if (hasTorch.HasValue)
+ return hasTorch.Value;
+
+ if (!context.PackageManager.HasSystemFeature(PackageManager.FeatureCameraFlash))
+ {
+ Android.Util.Log.Info(MobileBarcodeScanner.TAG, "Flash not supported on this device");
+ return false;
+ }
+
+ if (cameraController.Camera == null)
+ {
+ Android.Util.Log.Info(MobileBarcodeScanner.TAG, "Run camera first");
+ return false;
+ }
+
+ var characteristics = cameraManager.GetCameraCharacteristics(cameraController.CameraId.ToString());
+ var cameraHasTorch = ((Java.Lang.Boolean)characteristics.Get(CameraCharacteristics.FlashInfoAvailable)).BooleanValue();
+
+ if (cameraHasTorch)
+ hasTorch = ZXing.Net.Mobile.Android.PermissionsHandler.IsTorchPermissionDeclared();
+
+ return hasTorch != null && hasTorch.Value;
+ }
+ }
+
+ public bool IsEnabled { get; private set; }
+
+ public void TurnOn() => Enable(true);
+
+ public void TurnOff() => Enable(false);
+
+ public void Toggle() => Enable(!IsEnabled);
+
+ private void Enable(bool state)
+ {
+ if (!IsSupported || IsEnabled == state)
+ return;
+
+ if (cameraController.Camera == null)
+ {
+ Android.Util.Log.Info(MobileBarcodeScanner.TAG, "NULL Camera, cannot toggle torch");
+ return;
+ }
+
+ cameraController.EnableTorch(state);
+ IsEnabled = state;
+ }
+ }
+}
diff --git a/ZXing.Net.Mobile/Android/DebugHelper.android.cs b/ZXing.Net.Mobile/Android/DebugHelper.android.cs
new file mode 100644
index 000000000..13a7763d6
--- /dev/null
+++ b/ZXing.Net.Mobile/Android/DebugHelper.android.cs
@@ -0,0 +1,72 @@
+using System.IO;
+using System.Net.Http;
+using Android.Graphics;
+using ZXing.Mobile.CameraAccess;
+
+namespace ZXing.Net.Mobile.Android
+{
+#if DEBUG
+ // Do not use this in production.
+ // This is a simple helping class to use with the ImageReceiver Tool
+ public static class DebugHelper
+ {
+ static int sendThreshold = 18;
+ static int sendCount;
+
+ // Used to check NV21 Output
+ static byte[] NV21toJPEG(byte[] nv21, int width, int height)
+ {
+ using (var mems = new MemoryStream())
+ {
+ var yuv = new YuvImage(nv21, ImageFormatType.Nv21, width, height, null);
+ yuv.CompressToJpeg(new Rect(0, 0, width, height), 100, mems);
+ return mems.ToArray();
+ }
+ }
+
+ public static void SendBytesToEndpoint(byte[] data, string endpoint, bool ignoreThreshold = false, params (string Key, string Value)[] headers)
+ {
+ if (!ignoreThreshold)
+ {
+ if (sendCount < sendThreshold)
+ {
+ sendCount++;
+ return;
+ }
+
+ sendCount = 0;
+ }
+
+ var httpClient = GetHttpClient();
+ var request = new HttpRequestMessage(HttpMethod.Post, endpoint);
+ request.Content = new ByteArrayContent(data);
+
+ foreach (var header in headers)
+ {
+ httpClient.DefaultRequestHeaders.Add(header.Key, header.Value);
+ }
+
+ httpClient.SendAsync(request);
+ }
+
+ public static void SendNV21toJPEGToEndpoint(byte[] data, int width, int height, string endpoint, bool ignoreThreshold = false, params (string Key, string Value)[] headers)
+ {
+ var jpeg = NV21toJPEG(data, width, height);
+ SendBytesToEndpoint(jpeg, endpoint, ignoreThreshold, headers);
+ }
+
+ public static void SendNV21toJPEGToEndpoint(CapturedImageData data, string endpoint, bool ignoreThreshold = false, params (string Key, string Value)[] headers)
+ => SendNV21toJPEGToEndpoint(data.Matrix, data.Width, data.Height, endpoint, ignoreThreshold, headers);
+
+ static HttpClient GetHttpClient()
+ {
+ var handler = new HttpClientHandler();
+ // we dont need to validate certificates
+ handler.ClientCertificateOptions = ClientCertificateOption.Manual;
+ handler.ServerCertificateCustomValidationCallback = (_, __, ___, ____) => true;
+
+ return new HttpClient(handler);
+ }
+ }
+#endif
+}
diff --git a/ZXing.Net.Mobile/Android/DeviceOrientationData.android.cs b/ZXing.Net.Mobile/Android/DeviceOrientationData.android.cs
new file mode 100644
index 000000000..cf72ecb83
--- /dev/null
+++ b/ZXing.Net.Mobile/Android/DeviceOrientationData.android.cs
@@ -0,0 +1,20 @@
+using Android.Content.Res;
+
+namespace ZXing.Net.Mobile.Android
+{
+ public class DeviceOrientationData
+ {
+ public Orientation OrientationMode { get; }
+
+ public int DeviceOrientation { get; }
+
+ public int SensorRotation { get; }
+
+ public DeviceOrientationData(Orientation orientationMode, int orientation, int sensorRotation)
+ {
+ OrientationMode = orientationMode;
+ DeviceOrientation = orientation;
+ SensorRotation = sensorRotation;
+ }
+ }
+}
diff --git a/ZXing.Net.Mobile/Android/DeviceOrientationEventListener.android..cs b/ZXing.Net.Mobile/Android/DeviceOrientationEventListener.android..cs
new file mode 100644
index 000000000..e03175646
--- /dev/null
+++ b/ZXing.Net.Mobile/Android/DeviceOrientationEventListener.android..cs
@@ -0,0 +1,42 @@
+using Android.Content;
+using Android.Hardware;
+using Android.Runtime;
+using Android.Views;
+
+namespace ZXing.Net.Mobile.Android
+{
+ public class DeviceOrientationEventListener : OrientationEventListener
+ {
+ public int Orientation { get; private set; }
+
+ public bool IsEnabled { get; private set; } = false;
+
+ public DeviceOrientationEventListener(Context context)
+ : base(context)
+ {
+ }
+
+ public DeviceOrientationEventListener(Context context, [GeneratedEnum] SensorDelay rate)
+ : base(context, rate)
+ {
+ }
+
+ public override void OnOrientationChanged(int orientation)
+ {
+ if (orientation != OrientationUnknown)
+ Orientation = orientation;
+ }
+
+ public override void Enable()
+ {
+ base.Enable();
+ IsEnabled = true;
+ }
+
+ public override void Disable()
+ {
+ base.Disable();
+ IsEnabled = false;
+ }
+ }
+}
diff --git a/ZXing.Net.Mobile/Android/FastJavaArrayEx.android.cs b/ZXing.Net.Mobile/Android/FastJavaArrayEx.android.cs
deleted file mode 100644
index 14584e001..000000000
--- a/ZXing.Net.Mobile/Android/FastJavaArrayEx.android.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System;
-using System.Runtime.InteropServices;
-using ApxLabs.FastAndroidCamera;
-
-namespace ZXing.Mobile
-{
- public static class FastJavaArrayEx
- {
- public static void BlockCopyTo(this FastJavaByteArray self, int sourceIndex, byte[] array, int arrayIndex, int length)
- {
- unsafe
- {
- Marshal.Copy(new IntPtr(self.Raw + sourceIndex), array, arrayIndex, Math.Min(length, Math.Min(self.Count, array.Length - arrayIndex)));
- }
- }
- }
-}
\ No newline at end of file
diff --git a/ZXing.Net.Mobile/Android/FastJavaByteArrayYUVLuminanceSource.android.cs b/ZXing.Net.Mobile/Android/FastJavaByteArrayYUVLuminanceSource.android.cs
deleted file mode 100644
index 1137377ac..000000000
--- a/ZXing.Net.Mobile/Android/FastJavaByteArrayYUVLuminanceSource.android.cs
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright 2009 ZXing authors
- * Modifications copyright 2016 kasper@byolimit.com
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-using ApxLabs.FastAndroidCamera;
-
-using System;
-
-namespace ZXing.Mobile
-{
- ///
- /// This object extends LuminanceSource around an array of YUV data returned from the camera driver,
- /// with the option to crop to a rectangle within the full data. This can be used to exclude
- /// superfluous pixels around the perimeter and speed up decoding.
- /// It works for any pixel format where the Y channel is planar and appears first, including
- /// YCbCr_420_SP and YCbCr_422_SP.
- ///
- ///
- /// Builds upon PlanarYUVLuminanceSource from ZXing.NET, which is a .Net port of ZXing. The original code
- /// was authored by
- /// @author dswitkin@google.com (Daniel Switkin)
- ///
- public sealed class FastJavaByteArrayYUVLuminanceSource : BaseLuminanceSource
- {
- private readonly FastJavaByteArray _yuv;
- private readonly int _dataWidth;
- private readonly int _dataHeight;
- private readonly int _left;
- private readonly int _top;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The yuv data.
- /// Width of the data.
- /// Height of the data.
- /// The left.
- /// The top.
- /// The width.
- /// The height.
- /// if set to true [reverse horiz].
- public FastJavaByteArrayYUVLuminanceSource(FastJavaByteArray yuv,
- int dataWidth,
- int dataHeight,
- int left,
- int top,
- int width,
- int height)
- : base(width, height)
- {
- if (left < 0)
- throw new ArgumentException("Negative value", nameof(left));
-
- if (top < 0)
- throw new ArgumentException("Negative value", nameof(top));
-
- if (width < 0)
- throw new ArgumentException("Negative value", nameof(width));
-
- if (height < 0)
- throw new ArgumentException("Negative value", nameof(height));
-
- if (left + width > dataWidth || top + height > dataHeight)
- {
- throw new ArgumentException("Crop rectangle does not fit within image data.");
- }
-
- _yuv = yuv;
- _dataWidth = dataWidth;
- _dataHeight = dataHeight;
- _left = left;
- _top = top;
- }
-
- ///
- /// Fetches one row of luminance data from the underlying platform's bitmap. Values range from
- /// 0 (black) to 255 (white). Because Java does not have an unsigned byte type, callers will have
- /// to bitwise and with 0xff for each value. It is preferable for implementations of this method
- /// to only fetch this row rather than the whole image, since no 2D Readers may be installed and
- /// getMatrix() may never be called.
- ///
- /// The row to fetch, 0 <= y < Height.
- /// An optional preallocated array. If null or too small, it will be ignored.
- /// Always use the returned object, and ignore the .length of the array.
- ///
- /// An array containing the luminance data of the requested row.
- ///
- override public byte[] getRow(int y, byte[] row)
- {
- if (y < 0 || y >= Height)
- throw new ArgumentException("Requested row is outside the image: " + y, nameof(y));
-
- var width = Width;
- if (row == null || row.Length < width)
- row = new byte[width]; // ensure we have room for the row
-
- var offset = (y + _top) * _dataWidth + _left;
- _yuv.BlockCopyTo(offset, row, 0, width);
- return row;
- }
-
- override public byte[] Matrix
- {
- get
- {
- var width = Width;
- var height = Height;
-
- var area = width * height;
- var matrix = new byte[area];
- var inputOffset = _top * _dataWidth + _left;
-
- // If the width matches the full width of the underlying data, perform a single copy.
- if (width == _dataWidth)
- {
- _yuv.BlockCopyTo(inputOffset, matrix, 0, area);
- return matrix;
- }
-
- // Otherwise copy one cropped row at a time.
- for (var y = 0; y < height; y++)
- {
- var outputOffset = y * width;
- _yuv.BlockCopyTo(inputOffset, matrix, outputOffset, width);
- inputOffset += _dataWidth;
- }
- return matrix;
- }
- }
-
- /// Whether this subclass supports cropping.
- override public bool CropSupported
- => true;
-
- ///
- /// Returns a new object with cropped image data. Implementations may keep a reference to the
- /// original data rather than a copy. Only callable if CropSupported is true.
- ///
- /// The left coordinate, 0 <= left < Width.
- /// The top coordinate, 0 <= top <= Height.
- /// The width of the rectangle to crop.
- /// The height of the rectangle to crop.
- ///
- /// A cropped version of this object.
- ///
- override public LuminanceSource crop(int left, int top, int width, int height)
- => new FastJavaByteArrayYUVLuminanceSource(_yuv,
- _dataWidth,
- _dataHeight,
- _left + left,
- _top + top,
- width,
- height);
-
- // Called when rotating.
- // todo: This partially defeats the purpose as we traffic in byte[] luminances
- protected override LuminanceSource CreateLuminanceSource(byte[] newLuminances, int width, int height)
- => new PlanarYUVLuminanceSource(newLuminances, width, height, 0, 0, width, height, false);
- }
-}
\ No newline at end of file
diff --git a/ZXing.Net.Mobile/Android/PlanarNV21LuminanceSource.android.cs b/ZXing.Net.Mobile/Android/PlanarNV21LuminanceSource.android.cs
new file mode 100644
index 000000000..0ac6936e0
--- /dev/null
+++ b/ZXing.Net.Mobile/Android/PlanarNV21LuminanceSource.android.cs
@@ -0,0 +1,131 @@
+using System;
+using Android.Content.Res;
+
+namespace ZXing.Net.Mobile.Android
+{
+ public class PlanarNV21LuminanceSource : BaseLuminanceSource
+ {
+ DeviceOrientationData orientationData = null;
+
+ public override bool CropSupported => false;
+
+ public override bool RotateSupported => true;
+
+
+ public PlanarNV21LuminanceSource(byte[] nv21Data, int width, int height, DeviceOrientationData orientationData, bool useOrientationDataToCorrect)
+ : base(width, height)
+ {
+ this.orientationData = orientationData;
+ base.luminances = nv21Data;
+ Width = width;
+ Height = height;
+
+ if (useOrientationDataToCorrect && orientationData == null)
+ throw new ArgumentNullException($"{orientationData} can't be null when correction is requested");
+
+ if (orientationData != null && useOrientationDataToCorrect)
+ ValidateRotation();
+ }
+
+ public PlanarNV21LuminanceSource(byte[] nv21Data, int width, int height)
+ : this(nv21Data, width, height, null, false)
+ {
+ }
+
+ void ValidateRotation()
+ {
+ if (orientationData.SensorRotation % 90 != 0) // we don't support weird sensor orientations
+ {
+ return;
+ }
+
+ var rotateBy = 0;
+ if (orientationData.OrientationMode == Orientation.Landscape)
+ {
+ if (orientationData.DeviceOrientation >= 180) // Navigation on the left, Header on the left (270°)
+ {
+ rotateBy = 270; // 270 + 90 = 360 || 360 zeros out so nothing to do
+ }
+ else if (orientationData.DeviceOrientation < 180) // Navigation on the left, Header on the Right (90°)
+ {
+ rotateBy = 90;
+ }
+ }
+ else
+ {
+ if (orientationData.DeviceOrientation > 175 && orientationData.DeviceOrientation < 185) // Upside down and not landscape mode
+ {
+ rotateBy = 180;
+ }
+ }
+
+ rotateBy += orientationData.SensorRotation;
+ rotateBy %= 360; // Normalize
+
+ var rotateResult = RotateNV21(luminances, Width, Height, rotateBy);
+ luminances = rotateResult.NV21;
+ Width = rotateResult.Width;
+ Height = rotateResult.Height;
+ }
+
+ protected override LuminanceSource CreateLuminanceSource(byte[] newLuminances, int width, int height)
+ => new PlanarNV21LuminanceSource(newLuminances, width, height, orientationData, false);
+
+ public override LuminanceSource rotateCounterClockwise() => GetRotatedLuminanceSource(270);
+
+ public LuminanceSource RotateClockwise() => GetRotatedLuminanceSource(90);
+
+ public LuminanceSource Mirror() => GetRotatedLuminanceSource(180);
+
+ LuminanceSource GetRotatedLuminanceSource(int rotation)
+ {
+ var rotateResult = RotateNV21(luminances, Width, Height, rotation);
+ return CreateLuminanceSource(rotateResult.NV21, rotateResult.Width, rotateResult.Height);
+ }
+
+ // https://stackoverflow.com/questions/6853401/camera-pixels-rotated/31425229#31425229
+ public static (byte[] NV21, int Width, int Height) RotateNV21(byte[] nv21, int width, int height, int rotation)
+ {
+ if (rotation == 0)
+ return (nv21, width, height);
+
+ if (rotation % 90 != 0 || rotation < 0 || rotation > 270)
+ {
+ throw new ArgumentException("0 <= rotation < 360, rotation % 90 == 0");
+ }
+
+ var output = new byte[nv21.Length];
+ var frameSize = width * height;
+ var swap = rotation % 180 != 0;
+ var xflip = rotation % 270 != 0;
+ var yflip = rotation >= 180;
+
+ for (var row = 0; row < height; row++)
+ {
+ for (var col = 0; col < width; col++)
+ {
+ var yInPos = row * width + col;
+ var uInPos = frameSize + (row >> 1) * width + (col & ~1);
+ var vInPos = uInPos + 1;
+
+ var widthOut = swap ? height : width;
+ var heightOut = swap ? width : height;
+ var colSwapped = swap ? row : col;
+ var rowSwapped = swap ? col : row;
+ var colOut = xflip ? widthOut - colSwapped - 1 : colSwapped;
+ var rowOut = yflip ? heightOut - rowSwapped - 1 : rowSwapped;
+
+ var yOutPos = rowOut * widthOut + colOut;
+ var uOutPos = frameSize + (rowOut >> 1) * widthOut + (colOut & ~1);
+ var vOutPos = uOutPos + 1;
+
+ output[yOutPos] = (byte)(0xff & nv21[yInPos]);
+ output[uOutPos] = (byte)(0xff & nv21[uInPos]);
+ output[vOutPos] = (byte)(0xff & nv21[vInPos]);
+ }
+ }
+
+ return (output, swap ? height : width, swap ? width : height);
+ }
+ }
+}
diff --git a/ZXing.Net.Mobile/Android/ZXingSurfaceView.android.cs b/ZXing.Net.Mobile/Android/ZXingSurfaceView.android.cs
index 0fe0c87f1..4c9049e49 100644
--- a/ZXing.Net.Mobile/Android/ZXingSurfaceView.android.cs
+++ b/ZXing.Net.Mobile/Android/ZXingSurfaceView.android.cs
@@ -5,156 +5,166 @@
using Android.Graphics;
using ZXing.Mobile.CameraAccess;
using ZXing.Net.Mobile.Android;
+using System.Threading.Tasks;
+using System.Threading;
namespace ZXing.Mobile
{
- public class ZXingSurfaceView : SurfaceView, ISurfaceHolderCallback, IScannerView, IScannerSessionHost
- {
- public ZXingSurfaceView(Context context, MobileBarcodeScanningOptions options)
- : base(context)
- {
- ScanningOptions = options ?? new MobileBarcodeScanningOptions();
- Init();
- }
-
- protected ZXingSurfaceView(IntPtr javaReference, JniHandleOwnership transfer)
- : base(javaReference, transfer) => Init();
-
- bool addedHolderCallback = false;
-
- void Init()
- {
- if (cameraAnalyzer == null)
- cameraAnalyzer = new CameraAnalyzer(this, this);
-
- cameraAnalyzer.ResumeAnalysis();
-
- if (!addedHolderCallback)
- {
- Holder.AddCallback(this);
- Holder.SetType(SurfaceType.PushBuffers);
- addedHolderCallback = true;
- }
- }
-
- public async void SurfaceCreated(ISurfaceHolder holder)
- {
- await PermissionsHandler.RequestPermissionsAsync();
-
- cameraAnalyzer.SetupCamera();
-
- surfaceCreated = true;
- }
-
- public async void SurfaceChanged(ISurfaceHolder holder, Format format, int wx, int hx)
- => cameraAnalyzer.RefreshCamera();
-
- public async void SurfaceDestroyed(ISurfaceHolder holder)
- {
- try
- {
- if (addedHolderCallback)
- {
- Holder.RemoveCallback(this);
- addedHolderCallback = false;
- }
- }
- catch { }
-
- cameraAnalyzer.ShutdownCamera();
- }
-
- public override bool OnTouchEvent(MotionEvent e)
- {
- var r = base.OnTouchEvent(e);
-
- switch (e.Action)
- {
- case MotionEventActions.Down:
- return true;
- case MotionEventActions.Up:
- var touchX = e.GetX();
- var touchY = e.GetY();
- AutoFocus((int)touchX, (int)touchY);
- break;
- }
-
- return r;
- }
-
- public void AutoFocus()
- => cameraAnalyzer.AutoFocus();
-
- public void AutoFocus(int x, int y)
- => cameraAnalyzer.AutoFocus(x, y);
-
- public void StartScanning(Action scanResultCallback, MobileBarcodeScanningOptions options = null)
- {
- cameraAnalyzer.SetupCamera();
-
- ScanningOptions = options ?? MobileBarcodeScanningOptions.Default;
-
- cameraAnalyzer.BarcodeFound = (result) =>
- scanResultCallback?.Invoke(result);
- cameraAnalyzer.ResumeAnalysis();
- }
-
- public void StopScanning()
- => cameraAnalyzer.ShutdownCamera();
-
- public void PauseAnalysis()
- => cameraAnalyzer.PauseAnalysis();
-
- public void ResumeAnalysis()
- => cameraAnalyzer.ResumeAnalysis();
-
- public void Torch(bool on)
- {
- if (on)
- cameraAnalyzer.Torch.TurnOn();
- else
- cameraAnalyzer.Torch.TurnOff();
- }
-
- public void ToggleTorch()
- => cameraAnalyzer.Torch.Toggle();
-
- public MobileBarcodeScanningOptions ScanningOptions { get; set; }
-
- public bool IsTorchOn => cameraAnalyzer.Torch.IsEnabled;
-
- public bool IsAnalyzing => cameraAnalyzer.IsAnalyzing;
-
- CameraAnalyzer cameraAnalyzer;
- bool surfaceCreated;
-
- public bool HasTorch => cameraAnalyzer.Torch.IsSupported;
-
- protected override void OnAttachedToWindow()
- {
- base.OnAttachedToWindow();
-
- // Reinit things
- Init();
- }
-
- protected override void OnWindowVisibilityChanged(ViewStates visibility)
- {
- base.OnWindowVisibilityChanged(visibility);
- if (visibility == ViewStates.Visible)
- Init();
- }
-
- public override async void OnWindowFocusChanged(bool hasWindowFocus)
- {
- base.OnWindowFocusChanged(hasWindowFocus);
-
- if (!hasWindowFocus)
- return;
-
- //only refresh the camera if the surface has already been created. Fixed #569
- if (surfaceCreated)
- cameraAnalyzer.RefreshCamera();
- }
- }
+ public class ZXingSurfaceView : SurfaceView, ISurfaceHolderCallback, IScannerView, IScannerSessionHost
+ {
+ public ZXingSurfaceView(Context context, MobileBarcodeScanningOptions options)
+ : base(context)
+ {
+ ScanningOptions = options ?? new MobileBarcodeScanningOptions();
+ Init();
+ }
+
+ protected ZXingSurfaceView(IntPtr javaReference, JniHandleOwnership transfer)
+ : base(javaReference, transfer) => Init();
+
+ bool addedHolderCallback = false;
+
+ void Init()
+ {
+ if (cameraAnalyzer == null)
+ cameraAnalyzer = new CameraAnalyzer(this, this);
+
+ cameraAnalyzer.ResumeAnalysis();
+
+ if (!addedHolderCallback)
+ {
+ Holder.AddCallback(this);
+ Holder.SetType(SurfaceType.PushBuffers);
+ addedHolderCallback = true;
+ }
+ }
+
+ public async void SurfaceCreated(ISurfaceHolder holder)
+ {
+ await PermissionsHandler.RequestPermissionsAsync();
+
+ cameraAnalyzer.SetupCamera();
+
+ surfaceCreated = true;
+ surfaceCreatedResetEvent.Set();
+ }
+
+ public async void SurfaceChanged(ISurfaceHolder holder, Format format, int wx, int hx)
+ {
+ cameraAnalyzer.RefreshCamera();
+ }
+
+ public async void SurfaceDestroyed(ISurfaceHolder holder)
+ {
+ try
+ {
+ if (addedHolderCallback)
+ {
+ Holder.RemoveCallback(this);
+ addedHolderCallback = false;
+ }
+ }
+ catch { }
+
+ cameraAnalyzer.ShutdownCamera();
+ }
+
+ public override bool OnTouchEvent(MotionEvent e)
+ {
+ var r = base.OnTouchEvent(e);
+
+ switch (e.Action)
+ {
+ case MotionEventActions.Down:
+ return true;
+ case MotionEventActions.Up:
+ var touchX = e.GetX();
+ var touchY = e.GetY();
+ AutoFocus((int)touchX, (int)touchY);
+ break;
+ }
+
+ return r;
+ }
+
+ public void AutoFocus()
+ => cameraAnalyzer.AutoFocus();
+
+ public void AutoFocus(int x, int y)
+ => cameraAnalyzer.AutoFocus(x, y);
+
+ public void StartScanning(Action scanResultCallback, MobileBarcodeScanningOptions options = null)
+ {
+ Task.Run(() =>
+ {
+ surfaceCreatedResetEvent.Wait();
+ surfaceCreatedResetEvent.Reset();
+
+ ScanningOptions = options ?? MobileBarcodeScanningOptions.Default;
+
+ cameraAnalyzer.BarcodeFound = (result) =>
+ scanResultCallback?.Invoke(result);
+ cameraAnalyzer.ResumeAnalysis();
+ });
+ }
+
+ public void StopScanning()
+ => cameraAnalyzer.ShutdownCamera();
+
+ public void PauseAnalysis()
+ => cameraAnalyzer.PauseAnalysis();
+
+ public void ResumeAnalysis()
+ => cameraAnalyzer.ResumeAnalysis();
+
+ public void Torch(bool on)
+ {
+ if (on)
+ cameraAnalyzer.Torch.TurnOn();
+ else
+ cameraAnalyzer.Torch.TurnOff();
+ }
+
+ public void ToggleTorch()
+ => cameraAnalyzer.Torch.Toggle();
+
+ public MobileBarcodeScanningOptions ScanningOptions { get; set; }
+
+ public bool IsTorchOn => cameraAnalyzer.Torch.IsEnabled;
+
+ public bool IsAnalyzing => cameraAnalyzer.IsAnalyzing;
+
+ CameraAnalyzer cameraAnalyzer;
+ bool surfaceCreated;
+ ManualResetEventSlim surfaceCreatedResetEvent = new ManualResetEventSlim(false);
+
+ public bool HasTorch => cameraAnalyzer.Torch.IsSupported;
+
+ protected override void OnAttachedToWindow()
+ {
+ base.OnAttachedToWindow();
+
+ // Reinit things
+ Init();
+ }
+
+ protected override void OnWindowVisibilityChanged(ViewStates visibility)
+ {
+ base.OnWindowVisibilityChanged(visibility);
+ if (visibility == ViewStates.Visible)
+ Init();
+ }
+
+ public override async void OnWindowFocusChanged(bool hasWindowFocus)
+ {
+ base.OnWindowFocusChanged(hasWindowFocus);
+
+ if (!hasWindowFocus)
+ return;
+
+ //only refresh the camera if the surface has already been created. Fixed #569
+ if (surfaceCreated)
+ cameraAnalyzer.RefreshCamera();
+ }
+ }
}
diff --git a/ZXing.Net.Mobile/MobileBarcodeScanningOptions.shared.cs b/ZXing.Net.Mobile/MobileBarcodeScanningOptions.shared.cs
index 2ab08614a..14f0f69ee 100644
--- a/ZXing.Net.Mobile/MobileBarcodeScanningOptions.shared.cs
+++ b/ZXing.Net.Mobile/MobileBarcodeScanningOptions.shared.cs
@@ -1,121 +1,118 @@
-using System;
-using System.Collections;
using System.Collections.Generic;
using System.Linq;
-using ZXing;
namespace ZXing.Mobile
{
- public class MobileBarcodeScanningOptions
- {
- ///
- /// Camera resolution selector delegate, must return the selected Resolution from the list of available resolutions
- ///
- public delegate CameraResolution CameraResolutionSelectorDelegate(List availableResolutions);
+ public class MobileBarcodeScanningOptions
+ {
+ ///
+ /// Camera resolution selector delegate, must return the selected Resolution from the list of available resolutions
+ ///
+ public delegate CameraResolution CameraResolutionSelectorDelegate(List availableResolutions);
- public MobileBarcodeScanningOptions()
- {
- PossibleFormats = new List();
- //this.AutoRotate = true;
- DelayBetweenAnalyzingFrames = 150;
- InitialDelayBeforeAnalyzingFrames = 300;
- DelayBetweenContinuousScans = 1000;
- UseNativeScanning = false;
- }
+ public MobileBarcodeScanningOptions()
+ {
+ PossibleFormats = new List();
+ //this.AutoRotate = true;
+ DelayBetweenAnalyzingFrames = 150;
+ InitialDelayBeforeAnalyzingFrames = 300;
+ DelayBetweenContinuousScans = 1000;
+ UseNativeScanning = false;
+ }
- public CameraResolutionSelectorDelegate CameraResolutionSelector { get; set; }
+ public CameraResolutionSelectorDelegate CameraResolutionSelector { get; set; }
- public IEnumerable PossibleFormats { get; set; }
+ public IEnumerable PossibleFormats { get; set; }
- public bool? TryHarder { get; set; }
+ public bool? TryHarder { get; set; }
- public bool? PureBarcode { get; set; }
+ public bool? PureBarcode { get; set; }
- public bool? AutoRotate { get; set; }
+ public bool? AutoRotate { get; set; }
- public bool? UseCode39ExtendedMode { get; set; }
+ public bool? UseCode39ExtendedMode { get; set; }
- public string CharacterSet { get; set; }
+ public string CharacterSet { get; set; }
- public bool? TryInverted { get; set; }
+ public bool? TryInverted { get; set; }
- public bool? UseFrontCameraIfAvailable { get; set; }
+ public bool? UseFrontCameraIfAvailable { get; set; }
- public bool? AssumeGS1 { get; set; }
+ public bool? AssumeGS1 { get; set; }
- public bool DisableAutofocus { get; set; }
+ public bool DisableAutofocus { get; set; }
- public bool UseNativeScanning { get; set; }
+ public bool UseNativeScanning { get; set; }
- public int DelayBetweenContinuousScans { get; set; }
+ public int DelayBetweenContinuousScans { get; set; }
- public int DelayBetweenAnalyzingFrames { get; set; }
- public int InitialDelayBeforeAnalyzingFrames { get; set; }
+ public int DelayBetweenAnalyzingFrames { get; set; }
+ public int InitialDelayBeforeAnalyzingFrames { get; set; }
- public static MobileBarcodeScanningOptions Default
- {
- get { return new MobileBarcodeScanningOptions(); }
- }
+ public static MobileBarcodeScanningOptions Default
+ {
+ get { return new MobileBarcodeScanningOptions(); }
+ }
- public BarcodeReaderGeneric BuildBarcodeReader()
- {
- var reader = new BarcodeReaderGeneric();
- if (TryHarder.HasValue)
- reader.Options.TryHarder = TryHarder.Value;
- if (PureBarcode.HasValue)
- reader.Options.PureBarcode = PureBarcode.Value;
- if (AutoRotate.HasValue)
- reader.AutoRotate = AutoRotate.Value;
- if (UseCode39ExtendedMode.HasValue)
- reader.Options.UseCode39ExtendedMode = UseCode39ExtendedMode.Value;
- if (!string.IsNullOrEmpty(CharacterSet))
- reader.Options.CharacterSet = CharacterSet;
- if (TryInverted.HasValue)
- reader.TryInverted = TryInverted.Value;
- if (AssumeGS1.HasValue)
- reader.Options.AssumeGS1 = AssumeGS1.Value;
+ public BarcodeReaderGeneric BuildBarcodeReader()
+ {
+ var reader = new BarcodeReaderGeneric();
+ if (TryHarder.HasValue)
+ reader.Options.TryHarder = TryHarder.Value;
+ if (PureBarcode.HasValue)
+ reader.Options.PureBarcode = PureBarcode.Value;
+ if (AutoRotate.HasValue)
+ reader.AutoRotate = AutoRotate.Value;
+ if (UseCode39ExtendedMode.HasValue)
+ reader.Options.UseCode39ExtendedMode = UseCode39ExtendedMode.Value;
+ if (!string.IsNullOrEmpty(CharacterSet))
+ reader.Options.CharacterSet = CharacterSet;
+ if (TryInverted.HasValue)
+ reader.Options.TryInverted = TryInverted.Value;
+ if (AssumeGS1.HasValue)
+ reader.Options.AssumeGS1 = AssumeGS1.Value;
- if (PossibleFormats?.Any() ?? false)
- {
- reader.Options.PossibleFormats = new List();
+ if (PossibleFormats?.Any() ?? false)
+ {
+ reader.Options.PossibleFormats = new List();
- foreach (var pf in PossibleFormats)
- reader.Options.PossibleFormats.Add(pf);
- }
+ foreach (var pf in PossibleFormats)
+ reader.Options.PossibleFormats.Add(pf);
+ }
- return reader;
- }
+ return reader;
+ }
- public MultiFormatReader BuildMultiFormatReader()
- {
- var reader = new MultiFormatReader();
+ public MultiFormatReader BuildMultiFormatReader()
+ {
+ var reader = new MultiFormatReader();
- var hints = new Dictionary();
+ var hints = new Dictionary();
- if (TryHarder.HasValue && TryHarder.Value)
- hints.Add(DecodeHintType.TRY_HARDER, TryHarder.Value);
- if (PureBarcode.HasValue && PureBarcode.Value)
- hints.Add(DecodeHintType.PURE_BARCODE, PureBarcode.Value);
+ if (TryHarder.HasValue && TryHarder.Value)
+ hints.Add(DecodeHintType.TRY_HARDER, TryHarder.Value);
+ if (PureBarcode.HasValue && PureBarcode.Value)
+ hints.Add(DecodeHintType.PURE_BARCODE, PureBarcode.Value);
- if (PossibleFormats?.Any() ?? false)
- hints.Add(DecodeHintType.POSSIBLE_FORMATS, PossibleFormats);
+ if (PossibleFormats?.Any() ?? false)
+ hints.Add(DecodeHintType.POSSIBLE_FORMATS, PossibleFormats);
- reader.Hints = hints;
+ reader.Hints = hints;
- return reader;
- }
+ return reader;
+ }
- public CameraResolution GetResolution(List availableResolutions)
- {
- CameraResolution r = null;
+ public CameraResolution GetResolution(List availableResolutions)
+ {
+ CameraResolution r = null;
- var dg = CameraResolutionSelector;
+ var dg = CameraResolutionSelector;
- if (dg != null)
- r = dg(availableResolutions);
+ if (dg != null)
+ r = dg(availableResolutions);
- return r;
- }
- }
+ return r;
+ }
+ }
}
diff --git a/ZXing.Net.Mobile/ZXing.Net.Mobile.csproj b/ZXing.Net.Mobile/ZXing.Net.Mobile.csproj
index 029245573..5b9e6215d 100644
--- a/ZXing.Net.Mobile/ZXing.Net.Mobile.csproj
+++ b/ZXing.Net.Mobile/ZXing.Net.Mobile.csproj
@@ -86,7 +86,6 @@
-
@@ -106,6 +105,6 @@
-
+
\ No newline at end of file