Skip to content

Commit

Permalink
clean up
Browse files Browse the repository at this point in the history
  • Loading branch information
kfrancis committed Mar 26, 2024
1 parent f810c9b commit c0cf9aa
Show file tree
Hide file tree
Showing 11 changed files with 136 additions and 94 deletions.
30 changes: 11 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,22 +94,22 @@ No permissions are needed for Android.

### Dependency Injection

You will first need to register the `Feature` with the `MauiAppBuilder` following the same pattern that the .NET MAUI Essentials libraries follow.
You will first need to register the `OCR` with the `MauiAppBuilder` following the same pattern that the .NET MAUI Essentials libraries follow.

```csharp
builder.Services.AddSingleton(Feature.Default);
builder.Services.AddSingleton(OCR.Default);
```

You can then enable your classes to depend on `IFeature` as per the following example.

```csharp
public class FeatureViewModel
public class OcrViewModel
{
readonly IFeature feature;
readonly IOcrService ocr;

public FeatureViewModel(IFeature feature)
public FeatureViewModel(IOcrService ocr)
{
this.feature = feature;
this.ocr = ocr;
}

public void StartFeature()
Expand Down Expand Up @@ -138,7 +138,7 @@ public class FeatureViewModel
Console.WriteLine(feature.Thing);
};

Feature.Default.Start();
OCR.Default.Start();
}
}
```
Expand All @@ -149,29 +149,21 @@ Once you have created a `Feature` you can interact with it in the following ways

#### Events

##### `ReadingChanged`

Occurs when feature reading changes.

#### Properties

##### `IsSupported`

Gets a value indicating whether reading the feature is supported on this device.

##### `IsMonitoring`

Gets a value indicating whether the feature is actively being monitored.

#### Methods

##### `Start()`
##### `InitAsync()`

Start monitoring for changes to the feature.
Initialize the feature.

##### `Stop()`
##### `RecognizeTextAsync(byte[] imageData)`

Stop monitoring for changes to the feature.
Recognize text from an image.

# Acknowledgements

Expand Down
10 changes: 10 additions & 0 deletions samples/Plugin.Maui.OCR.Sample.sln
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Plugin.Maui.OCR", "..\src\P
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Plugin.Shared.OCR", "..\src\Plugin.Shared.OCR\Plugin.Shared.OCR.csproj", "{7858EB06-C087-47AD-89F1-1E1C3517910D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{35827FD5-AD36-4B01-8E49-2F6821444934}"
ProjectSection(SolutionItems) = preProject
..\.editorconfig = ..\.editorconfig
..\.gitattributes = ..\.gitattributes
..\.gitignore = ..\.gitignore
..\LICENSE = ..\LICENSE
..\nuget.png = ..\nuget.png
..\README.md = ..\README.md
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down
10 changes: 10 additions & 0 deletions samples/Plugin.Xamarin.OCR.Sample.sln
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "lib", "lib", "{D67C2A02-A3F
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample", "sample", "{601F61DD-ACC1-4A8E-A06D-59EA2345A05A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FD1651A6-E0DD-429D-AB9B-338B736D736D}"
ProjectSection(SolutionItems) = preProject
..\.editorconfig = ..\.editorconfig
..\.gitattributes = ..\.gitattributes
..\.gitignore = ..\.gitignore
..\LICENSE = ..\LICENSE
..\nuget.png = ..\nuget.png
..\README.md = ..\README.md
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@

namespace Plugin.Maui.OCR;

public static class Feature
public static class OCR
{
static IOcrService? defaultImplementation;
static IOcrService? s_defaultImplementation;

/// <summary>
/// Provides the default implementation for static usage of this API.
/// </summary>
public static IOcrService Default =>
defaultImplementation ??= new OcrImplementation();
s_defaultImplementation ??= new OcrImplementation();

internal static void SetDefault(IOcrService? implementation) =>
defaultImplementation = implementation;
s_defaultImplementation = implementation;
}
25 changes: 12 additions & 13 deletions src/Plugin.Maui.OCR/OcrImplementation.android.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Android.Gms.Tasks;
using Android.Gms.Tasks;
using Android.Graphics;
using Android.Util;
using Plugin.Shared.OCR;
Expand All @@ -25,9 +25,11 @@ public static OcrResult ProcessOcrResult(Java.Lang.Object result)
ocrResult.Lines.Add(line.Text);
foreach (var element in line.Elements)
{
var ocrElement = new OcrElement();
ocrElement.Text = element.Text;
ocrElement.Confidence = element.Confidence;
var ocrElement = new OcrElement
{
Text = element.Text,
Confidence = element.Confidence
};
ocrResult.Elements.Add(ocrElement);
}
}
Expand All @@ -36,13 +38,15 @@ public static OcrResult ProcessOcrResult(Java.Lang.Object result)
return ocrResult;
}

/// <inheritdoc/>
public Task InitAsync(System.Threading.CancellationToken ct = default)
{
// Initialization might not be required for ML Kit's on-device text recognition,
// but you can perform any necessary setup here.
return Task.CompletedTask;
}

/// <inheritdoc/>
public async Task<OcrResult> RecognizeTextAsync(byte[] imageData, System.Threading.CancellationToken ct = default)
{
var image = BitmapFactory.DecodeByteArray(imageData, 0, imageData.Length);
Expand All @@ -52,7 +56,7 @@ public async Task<OcrResult> RecognizeTextAsync(byte[] imageData, System.Threadi
return ProcessOcrResult(await ToAwaitableTask(textScanner.Process(inputImage).AddOnSuccessListener(new OnSuccessListener()).AddOnFailureListener(new OnFailureListener())));
}

private Task<Java.Lang.Object> ToAwaitableTask(global::Android.Gms.Tasks.Task task)
private static Task<Java.Lang.Object> ToAwaitableTask(global::Android.Gms.Tasks.Task task)
{
var taskCompletionSource = new TaskCompletionSource<Java.Lang.Object>();
var taskCompleteListener = new TaskCompleteListener(taskCompletionSource);
Expand All @@ -76,14 +80,9 @@ public void OnSuccess(Java.Lang.Object result)
}
}

internal class TaskCompleteListener : Java.Lang.Object, IOnCompleteListener
internal class TaskCompleteListener(TaskCompletionSource<Java.Lang.Object> tcs) : Java.Lang.Object, IOnCompleteListener
{
private readonly TaskCompletionSource<Java.Lang.Object> _taskCompletionSource;

public TaskCompleteListener(TaskCompletionSource<Java.Lang.Object> tcs)
{
_taskCompletionSource = tcs;
}
private readonly TaskCompletionSource<Java.Lang.Object> _taskCompletionSource = tcs;

public void OnComplete(global::Android.Gms.Tasks.Task task)
{
Expand All @@ -101,4 +100,4 @@ public void OnComplete(global::Android.Gms.Tasks.Task task)
}
}
}
}
}
24 changes: 10 additions & 14 deletions src/Plugin.Maui.OCR/OcrImplementation.macios.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Foundation;
using Foundation;
using Plugin.Shared.OCR;
using UIKit;
using Vision;
Expand All @@ -7,12 +7,13 @@ namespace Plugin.Maui.OCR;

partial class OcrImplementation : IOcrService
{
private static readonly object _initLock = new();
private bool _isInitialized = false;
private static readonly object s_initLock = new();
private bool _isInitialized;

/// <inheritdoc/>
public Task InitAsync(CancellationToken ct = default)
{
lock (_initLock)
lock (s_initLock)
{
if (_isInitialized) return Task.CompletedTask;
_isInitialized = true;
Expand All @@ -24,17 +25,15 @@ public Task InitAsync(CancellationToken ct = default)
return Task.CompletedTask;
}

/// <inheritdoc/>
public async Task<OcrResult> RecognizeTextAsync(byte[] imageData, CancellationToken ct = default)
{
if (!_isInitialized)
{
throw new InvalidOperationException("Init must be called before RecognizeTextAsync.");
}

if (ct.IsCancellationRequested)
{
throw new OperationCanceledException(ct);
}
ct.ThrowIfCancellationRequested();

var tcs = new TaskCompletionSource<OcrResult>(TaskCreationOptions.RunContinuationsAsynchronously);
ct.Register(() => tcs.TrySetCanceled());
Expand Down Expand Up @@ -62,7 +61,7 @@ public async Task<OcrResult> RecognizeTextAsync(byte[] imageData, CancellationTo
});

var ocrHandler = new VNImageRequestHandler(image.CGImage, new NSDictionary());
ocrHandler.Perform(new VNRequest[] { recognizeTextRequest }, out NSError performError);
ocrHandler.Perform(new VNRequest[] { recognizeTextRequest }, out var performError);

if (performError != null)
{
Expand Down Expand Up @@ -97,10 +96,7 @@ private static OcrResult ProcessRecognitionResults(VNRequest request)
ocrResult.Lines.Add(topCandidate.String);

// Splitting by spaces to create elements might not be accurate for all languages/scripts
topCandidate.String.Split(" ").ToList().ForEach(e =>
{
ocrResult.Elements.Add(new OcrResult.OcrElement { Text = e, Confidence = topCandidate.Confidence });
});
topCandidate.String.Split(" ").ToList().ForEach(e => ocrResult.Elements.Add(new OcrResult.OcrElement { Text = e, Confidence = topCandidate.Confidence }));
}
}

Expand All @@ -112,4 +108,4 @@ private static OcrResult ProcessRecognitionResults(VNRequest request)
{
return data != null ? new UIImage(NSData.FromArray(data)) : null;
}
}
}
7 changes: 5 additions & 2 deletions src/Plugin.Maui.OCR/OcrImplementation.net.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
using Plugin.Shared.OCR;
using Plugin.Shared.OCR;

namespace Plugin.Maui.OCR;

partial class OcrImplementation : IOcrService
{
/// <inheritdoc/>
public Task InitAsync(CancellationToken ct = default)
{
throw new NotImplementedException();
}

// TODO Implement your .NET specific code.
// This usually is a placeholder as .NET MAUI apps typically don't run on .NET generic targets unless through unit tests and such

/// <inheritdoc/>
public Task<OcrResult> RecognizeTextAsync(byte[] imageData, CancellationToken ct = default)
{
throw new NotImplementedException();
}
}
}
39 changes: 17 additions & 22 deletions src/Plugin.Maui.OCR/OcrImplementation.windows.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Plugin.Shared.OCR;
using Plugin.Shared.OCR;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Graphics.Imaging;
using Windows.Media.Ocr;
Expand All @@ -8,38 +8,33 @@ namespace Plugin.Maui.OCR;

partial class OcrImplementation : IOcrService
{
/// <inheritdoc/>
public Task InitAsync(CancellationToken ct = default)
{
// Windows OCR doesn't require explicit initialization.
return Task.CompletedTask;
}

// TODO Implement your Windows specific code
/// <inheritdoc/>
public async Task<Shared.OCR.OcrResult> RecognizeTextAsync(byte[] imageData, CancellationToken ct = default)
{
var ocrEngine = OcrEngine.TryCreateFromUserProfileLanguages();
if (ocrEngine == null)
{
throw new NotSupportedException("OCR not supported on this device or no languages are installed.");
}
var ocrEngine = OcrEngine.TryCreateFromUserProfileLanguages() ?? throw new NotSupportedException("OCR not supported on this device or no languages are installed.");

using (var stream = new InMemoryRandomAccessStream())
{
await stream.WriteAsync(imageData.AsBuffer());
stream.Seek(0);
using var stream = new InMemoryRandomAccessStream();
await stream.WriteAsync(imageData.AsBuffer());
stream.Seek(0);

var decoder = await BitmapDecoder.CreateAsync(stream);
var softwareBitmap = await decoder.GetSoftwareBitmapAsync();
var decoder = await BitmapDecoder.CreateAsync(stream);
var softwareBitmap = await decoder.GetSoftwareBitmapAsync();

var ocrResult = await ocrEngine.RecognizeAsync(softwareBitmap);
var ocrResult = await ocrEngine.RecognizeAsync(softwareBitmap);

var result = new Shared.OCR.OcrResult
{
AllText = ocrResult.Text,
// Further process the result as needed, e.g., extract lines, words, etc.
};
var result = new Shared.OCR.OcrResult
{
AllText = ocrResult.Text,
// Further process the result as needed, e.g., extract lines, words, etc.
};

return result;
}
return result;
}
}
}
7 changes: 6 additions & 1 deletion src/Plugin.Maui.OCR/Plugin.Maui.OCR.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0-android;net8.0-ios;net8.0-maccatalyst;net8.0</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net8.0-windows10.0.19041.0</TargetFrameworks>
Expand All @@ -8,6 +8,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UseMaui>true</UseMaui>
<LangVersion>latest</LangVersion>

<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">14.0</SupportedOSPlatformVersion>
Expand Down Expand Up @@ -72,6 +73,10 @@
<ItemGroup>
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="all" IsImplicitlyDefined="true" />
<PackageReference Include="Roslynator.Analyzers" Version="4.12.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Management" Version="7.0.0" Condition="$(TargetFramework.Contains('-windows')) == true" />
</ItemGroup>

Expand Down
Loading

0 comments on commit c0cf9aa

Please sign in to comment.