Skip to content

Commit d543ec9

Browse files
authored
Merge pull request #6 from CptWesley/spans
Spans
2 parents 9087aea + 99fcf26 commit d543ec9

17 files changed

+542
-138
lines changed

src/TesserNet.ImageSharp/ImageSharpTesseractExtensions.cs

+34-19
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System;
2-
using System.Runtime.InteropServices;
32
using System.Threading.Tasks;
43
using SixLabors.ImageSharp;
54
using SixLabors.ImageSharp.PixelFormats;
@@ -39,8 +38,20 @@ public static string Read(this ITesseract tesseract, Image image, Rectangle rect
3938
throw new ArgumentNullException(nameof(image));
4039
}
4140

42-
byte[] data = BitmapToBytes(image);
43-
return tesseract.Read(data, image.Width, image.Height, 4, rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
41+
if (image is not Image<Rgba32> bmp)
42+
{
43+
bmp = image.CloneAs<Rgba32>();
44+
}
45+
46+
IntPtr data = BitmapToBytes(bmp);
47+
string result = tesseract.Read(data, image.Width, image.Height, 4, rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
48+
49+
if (bmp != image)
50+
{
51+
bmp.Dispose();
52+
}
53+
54+
return result;
4455
}
4556

4657
/// <summary>
@@ -71,33 +82,37 @@ public static Task<string> ReadAsync(this ITesseract tesseract, Image image, Rec
7182
throw new ArgumentNullException(nameof(image));
7283
}
7384

74-
byte[] data = BitmapToBytes(image);
75-
return tesseract.ReadAsync(data, image.Width, image.Height, 4, rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
76-
}
77-
78-
private static byte[] BitmapToBytes(Image image)
79-
{
8085
if (image is not Image<Rgba32> bmp)
8186
{
8287
bmp = image.CloneAs<Rgba32>();
8388
}
8489

85-
if (!bmp.DangerousTryGetSinglePixelMemory(out Memory<Rgba32> memory))
90+
IntPtr data = BitmapToBytes(bmp);
91+
92+
Task<string> resultTask = tesseract.ReadAsync(data, image.Width, image.Height, 4, rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
93+
94+
return resultTask.ContinueWith(r =>
8695
{
87-
throw new TesseractException($"Could not get image pixels.");
88-
}
96+
if (bmp != image)
97+
{
98+
bmp.Dispose();
99+
}
89100

90-
// TODO: Add `Span<byte>, Memory<byte> overloads in ITesseract` to avoid memory allocation
91-
byte[] bytes = MemoryMarshal
92-
.AsBytes(memory.Span)
93-
.ToArray();
101+
return r.Result;
102+
});
103+
}
94104

95-
if (bmp != image)
105+
private static unsafe IntPtr BitmapToBytes(Image<Rgba32> image)
106+
{
107+
if (!image.DangerousTryGetSinglePixelMemory(out Memory<Rgba32> memory))
96108
{
97-
bmp.Dispose();
109+
throw new TesseractException($"Could not get image pixels.");
98110
}
99111

100-
return bytes;
112+
fixed (Rgba32* ptr = memory.Span)
113+
{
114+
return new IntPtr(ptr);
115+
}
101116
}
102117
}
103118
}

src/TesserNet.ImageSharp/TesserNet.ImageSharp.csproj

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
<LangVersion>9</LangVersion>
77
<Nullable>enable</Nullable>
88
<EnableNETAnalyzers>true</EnableNETAnalyzers>
9-
9+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
10+
1011
<Authors>Wesley Baartman</Authors>
1112
<PackageProjectUrl>https://github.com/CptWesley/TesserNet</PackageProjectUrl>
1213
<RepositoryUrl>https://github.com/CptWesley/TesserNet</RepositoryUrl>

src/TesserNet.SkiaSharp/SkiaSharpTesseractExtensions.cs

+9-4
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public static string Read(this ITesseract tesseract, SKBitmap image, SKRect rect
3737
throw new ArgumentNullException(nameof(image));
3838
}
3939

40-
byte[] data = BitmapToBytes(image);
40+
IntPtr data = BitmapToBytes(image);
4141
return tesseract.Read(data, image.Width, image.Height, 4, (int)rectangle.Left, (int)rectangle.Top, (int)rectangle.Width, (int)rectangle.Height);
4242
}
4343

@@ -69,7 +69,7 @@ public static Task<string> ReadAsync(this ITesseract tesseract, SKBitmap image,
6969
throw new ArgumentNullException(nameof(image));
7070
}
7171

72-
byte[] data = BitmapToBytes(image);
72+
IntPtr data = BitmapToBytes(image);
7373
return tesseract.ReadAsync(data, image.Width, image.Height, 4, (int)rectangle.Left, (int)rectangle.Top, (int)rectangle.Width, (int)rectangle.Height);
7474
}
7575

@@ -111,7 +111,12 @@ public static Task<string> ReadAsync(this ITesseract tesseract, SKImage image)
111111
public static Task<string> ReadAsync(this ITesseract tesseract, SKImage image, SKRect rectangle)
112112
=> tesseract.ReadAsync(SKBitmap.FromImage(image), rectangle);
113113

114-
private static byte[] BitmapToBytes(SKBitmap bmp)
115-
=> bmp.GetPixelSpan().ToArray();
114+
private static unsafe IntPtr BitmapToBytes(SKBitmap bmp)
115+
{
116+
fixed (byte* ptr = bmp.GetPixelSpan())
117+
{
118+
return new IntPtr(ptr);
119+
}
120+
}
116121
}
117122
}

src/TesserNet.SkiaSharp/TesserNet.SkiaSharp.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
<LangVersion>9</LangVersion>
77
<Nullable>enable</Nullable>
88
<EnableNETAnalyzers>true</EnableNETAnalyzers>
9+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
910

1011
<Authors>Wesley Baartman</Authors>
1112
<PackageProjectUrl>https://github.com/CptWesley/TesserNet</PackageProjectUrl>

src/TesserNet.System.Drawing/SystemDrawingTesseractExtensions.cs

+35-22
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
using System.Diagnostics.CodeAnalysis;
33
using System.Drawing;
44
using System.Drawing.Imaging;
5-
using System.Runtime.InteropServices;
65
using System.Threading.Tasks;
76

87
namespace TesserNet
@@ -28,16 +27,29 @@ public static string Read(this ITesseract tesseract, Image image)
2827
/// <param name="image">The image.</param>
2928
/// <param name="rectangle">The rectangle to perform OCR in.</param>
3029
/// <returns>The found text as a UTF8 string.</returns>
30+
[SuppressMessage("Reliability", "CA2000", Justification = "Bitmap is disposed if new one was created.")]
3131
public static string Read(this ITesseract tesseract, Image image, Rectangle rectangle)
3232
{
3333
if (tesseract is null)
3434
{
3535
throw new ArgumentNullException(nameof(tesseract));
3636
}
3737

38-
byte[] data = BitmapToBytes(image);
39-
int bpp = Image.GetPixelFormatSize(image.PixelFormat) / 8;
40-
return tesseract.Read(data, image.Width, image.Height, bpp, rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
38+
if (image is not Bitmap bmp)
39+
{
40+
bmp = new Bitmap(image);
41+
}
42+
43+
IntPtr data = BitmapToBytes(bmp);
44+
int bpp = Image.GetPixelFormatSize(bmp.PixelFormat) / 8;
45+
string result = tesseract.Read(data, image.Width, image.Height, bpp, rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
46+
47+
if (bmp != image)
48+
{
49+
bmp.Dispose();
50+
}
51+
52+
return result;
4153
}
4254

4355
/// <summary>
@@ -56,39 +68,40 @@ public static Task<string> ReadAsync(this ITesseract tesseract, Image image)
5668
/// <param name="image">The image.</param>
5769
/// <param name="rectangle">The rectangle to perform OCR in.</param>
5870
/// <returns>The found text as a UTF8 string.</returns>
71+
[SuppressMessage("Reliability", "CA2000", Justification = "Bitmap is disposed if new one was created.")]
5972
public static Task<string> ReadAsync(this ITesseract tesseract, Image image, Rectangle rectangle)
6073
{
6174
if (tesseract is null)
6275
{
6376
throw new ArgumentNullException(nameof(tesseract));
6477
}
6578

66-
byte[] data = BitmapToBytes(image);
67-
int bpp = Image.GetPixelFormatSize(image.PixelFormat) / 8;
68-
return tesseract.ReadAsync(data, image.Width, image.Height, bpp, rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
69-
}
70-
71-
[SuppressMessage("Reliability", "CA2000", Justification = "Bitmap is disposed if new one was created.")]
72-
private static byte[] BitmapToBytes(Image image)
73-
{
7479
if (image is not Bitmap bmp)
7580
{
7681
bmp = new Bitmap(image);
7782
}
7883

79-
BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat);
80-
IntPtr ptr = bmpData.Scan0;
81-
int size = bmp.Width * bmp.Height * Image.GetPixelFormatSize(bmp.PixelFormat) / 8;
82-
byte[] bytes = new byte[size];
83-
Marshal.Copy(ptr, bytes, 0, size);
84-
bmp.UnlockBits(bmpData);
84+
IntPtr data = BitmapToBytes(bmp);
85+
int bpp = Image.GetPixelFormatSize(image.PixelFormat) / 8;
86+
Task<string> resultTask = tesseract.ReadAsync(data, image.Width, image.Height, 4, rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
8587

86-
if (bmp != image)
88+
return resultTask.ContinueWith(r =>
8789
{
88-
bmp.Dispose();
89-
}
90+
if (bmp != image)
91+
{
92+
bmp.Dispose();
93+
}
9094

91-
return bytes;
95+
return r.Result;
96+
});
97+
}
98+
99+
private static IntPtr BitmapToBytes(Bitmap image)
100+
{
101+
BitmapData bmpData = image.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly, image.PixelFormat);
102+
IntPtr ptr = bmpData.Scan0;
103+
image.UnlockBits(bmpData);
104+
return ptr;
92105
}
93106
}
94107
}

src/TesserNet.Tests/ImageLoader.cs

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using System.IO;
2+
using System.Reflection;
3+
4+
namespace TesserNet.Tests
5+
{
6+
/// <summary>
7+
/// Used to load images.
8+
/// </summary>
9+
internal static class ImageLoader
10+
{
11+
/// <summary>
12+
/// Loads an image as a stream.
13+
/// </summary>
14+
/// <param name="fileName">The filename.</param>
15+
/// <returns>The stream.</returns>
16+
public static Stream LoadStream(string fileName)
17+
{
18+
Assembly asm = Assembly.GetExecutingAssembly();
19+
return asm.GetManifestResourceStream($"TesserNet.Tests.Resources.{fileName}");
20+
}
21+
22+
/// <summary>
23+
/// Loads an image as a byte array.
24+
/// </summary>
25+
/// <param name="fileName">The filename.</param>
26+
/// <returns>The stream.</returns>
27+
public static byte[] LoadByteArray(string fileName)
28+
{
29+
using MemoryStream ms = new MemoryStream();
30+
using Stream s = LoadStream(fileName);
31+
s.CopyTo(ms);
32+
return ms.ToArray();
33+
}
34+
}
35+
}

src/TesserNet.Tests/Resources/img.png

3.02 KB
Loading
+42-38
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,48 @@
11
<Project Sdk="Microsoft.NET.Sdk">
2-
<PropertyGroup>
3-
<TargetFramework>netcoreapp3.1</TargetFramework>
4-
<IsPackable>false</IsPackable>
5-
<CodeAnalysisRuleSet>../Ruleset.ruleset</CodeAnalysisRuleSet>
6-
<DocumentationFile>bin/$(AssemblyName).xml</DocumentationFile>
7-
</PropertyGroup>
2+
<PropertyGroup>
3+
<TargetFramework>netcoreapp3.1</TargetFramework>
4+
<IsPackable>false</IsPackable>
5+
<CodeAnalysisRuleSet>../Ruleset.ruleset</CodeAnalysisRuleSet>
6+
<DocumentationFile>bin/$(AssemblyName).xml</DocumentationFile>
7+
<EnableNETAnalyzers>true</EnableNETAnalyzers>
8+
</PropertyGroup>
89

9-
<PropertyGroup>
10-
<CollectCoverage>true</CollectCoverage>
11-
<CoverletOutputFormat>opencover</CoverletOutputFormat>
12-
<CoverletOutput>./bin/</CoverletOutput>
13-
<Include>[TesserNet]*</Include>
14-
</PropertyGroup>
10+
<PropertyGroup>
11+
<CollectCoverage>true</CollectCoverage>
12+
<CoverletOutputFormat>opencover</CoverletOutputFormat>
13+
<CoverletOutput>./bin/</CoverletOutput>
14+
<Include>[TesserNet]*</Include>
15+
</PropertyGroup>
1516

16-
<ItemGroup>
17-
<AdditionalFiles Include="..\stylecop.json" Link="stylecop.json" />
18-
</ItemGroup>
17+
<ItemGroup>
18+
<AdditionalFiles Include="../stylecop.json" Link="stylecop.json" />
19+
</ItemGroup>
1920

20-
<ItemGroup>
21-
<PackageReference Include="AssertNet" Version="2.0.0" />
22-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
23-
<PackageReference Include="xunit" Version="2.4.1" />
24-
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
25-
<PrivateAssets>all</PrivateAssets>
26-
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
27-
</PackageReference>
28-
<PackageReference Include="coverlet.msbuild" Version="2.9.0">
29-
<PrivateAssets>all</PrivateAssets>
30-
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
31-
</PackageReference>
32-
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.3.0">
33-
<PrivateAssets>all</PrivateAssets>
34-
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
35-
</PackageReference>
36-
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
37-
<PrivateAssets>all</PrivateAssets>
38-
</PackageReference>
39-
</ItemGroup>
21+
<ItemGroup>
22+
<PackageReference Include="AssertNet" Version="2.0.0" />
23+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
24+
<PackageReference Include="xunit" Version="2.4.1" />
25+
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
26+
<PrivateAssets>all</PrivateAssets>
27+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
28+
</PackageReference>
29+
<PackageReference Include="coverlet.msbuild" Version="3.1.2">
30+
<PrivateAssets>all</PrivateAssets>
31+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
32+
</PackageReference>
33+
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
34+
<PrivateAssets>all</PrivateAssets>
35+
</PackageReference>
36+
</ItemGroup>
4037

41-
<ItemGroup>
42-
<ProjectReference Include="..\TesserNet\TesserNet.csproj" />
43-
</ItemGroup>
38+
<ItemGroup>
39+
<ProjectReference Include="..\TesserNet.ImageSharp\TesserNet.ImageSharp.csproj" />
40+
<ProjectReference Include="..\TesserNet.SkiaSharp\TesserNet.SkiaSharp.csproj" />
41+
<ProjectReference Include="..\TesserNet.System.Drawing\TesserNet.System.Drawing.csproj" />
42+
<ProjectReference Include="..\TesserNet\TesserNet.csproj" />
43+
</ItemGroup>
44+
45+
<ItemGroup>
46+
<EmbeddedResource Include="Resources/**" />
47+
</ItemGroup>
4448
</Project>

0 commit comments

Comments
 (0)