Migrating from GdPicture.NET to IronOCR
This guide walks .NET developers through a complete migration from GdPicture.NET OCR to IronOCR. It covers the package swap, namespace changes, and practical before/after code patterns for every major OCR workflow, with particular focus on eliminating the integer image ID lifecycle that defines GdPicture's resource management model. No prior reading of the comparison article is required.
Why Migrate from GdPicture.NET
GdPicture.NET is a document imaging platform built for teams that need scanner integration, DICOM support, PDF editing, annotation, and OCR from a single vendor. When OCR is the only requirement, the platform's pricing and API architecture create friction that accumulates over time.
Plugin Cost for a Basic OCR Workflow. Extracting text from scanned PDFs and producing searchable output requires three separate license components: the Core SDK at roughly $4,000, the OCR Plugin at roughly $2,000, and the PDF Plugin at roughly $2,000. That is an $8,000 entry cost, plus 20% annual maintenance. Teams that need only OCR absorb the full pricing structure of a document imaging platform. IronOCR covers the same workflow — image OCR, PDF OCR, preprocessing, searchable PDF output — from a single package at $999 to $2,999 perpetual, with no per-feature licensing decisions. See the IronOCR licensing page for a full tier breakdown.
The Integer Image ID Lifecycle. Every image loaded through GdPicture returns an int. You pass that integer to OCR operations, then call ReleaseGdPictureImage with it when done. This pattern predates IDisposable. It works when followed correctly; it leaks memory when missed. In production services processing hundreds of documents daily, one missed release call on an error branch produces a memory growth that is genuinely difficult to diagnose. IronOCR's OcrInput implements IDisposable — a using statement eliminates the entire cleanup burden.
Version-Specific Namespace. GdPicture embeds the major version number in its namespace: using GdPicture14. Upgrading to the next major release requires updating that using directive in every source file that references GdPicture classes. A large application with OCR spread across dozens of services makes that a multi-hour find-and-replace task that delivers no functional improvement. IronOCR's namespace has been IronOcr across all major versions.
External Resource Folder Requirement. GdPicture OCR requires a ResourceFolder property pointing to a directory of .traineddata language files at runtime. That path works on a development machine, then breaks on Linux servers, Docker containers, and Azure App Service deployments where the directory structure does not exist. IronOCR bundles English language support inside the NuGet package; additional languages install as NuGet packages that travel with the build output.
Three-Component Initialization. A PDF OCR workflow in GdPicture requires instantiating GdPictureImaging, GdPictureOCR, and GdPicturePDF — three separate components with individual disposal requirements — plus the license manager registration and the resource folder assignment. IronOCR requires one line at startup and one class at call time.
No Native Thread Safety. GdPicture OCR instances are not thread-safe. Parallel document processing requires careful instance management and synchronization to avoid corrupted state. IronOCR is designed for concurrent use: create one IronTesseract per thread, or share a single instance under load — either pattern works without additional synchronization code.
The Fundamental Problem
GdPicture allocates memory for every image as a runtime-managed integer handle. Every RenderPageToGdPictureImage call in a TIFF processing loop creates a new allocation that must be released manually:
// GdPicture: every frame = new integer ID = manual release required
using var pdf = new GdPicturePDF();
pdf.LoadFromFile("multi-page.tiff", false);
var frameIds = new List<int>();
try
{
for (int i = 1; i <= pdf.GetPageCount(); i++)
{
pdf.SelectPage(i);
int frameId = pdf.RenderPageToGdPictureImage(200, false); // new allocation
if (frameId != 0) frameIds.Add(frameId);
// ... OCR call here ...
}
}
finally
{
foreach (var id in frameIds) _imaging.ReleaseGdPictureImage(id); // manual per-frame release
}
// GdPicture: every frame = new integer ID = manual release required
using var pdf = new GdPicturePDF();
pdf.LoadFromFile("multi-page.tiff", false);
var frameIds = new List<int>();
try
{
for (int i = 1; i <= pdf.GetPageCount(); i++)
{
pdf.SelectPage(i);
int frameId = pdf.RenderPageToGdPictureImage(200, false); // new allocation
if (frameId != 0) frameIds.Add(frameId);
// ... OCR call here ...
}
}
finally
{
foreach (var id in frameIds) _imaging.ReleaseGdPictureImage(id); // manual per-frame release
}
Imports System.Collections.Generic
' GdPicture: every frame = new integer ID = manual release required
Using pdf As New GdPicturePDF()
pdf.LoadFromFile("multi-page.tiff", False)
Dim frameIds As New List(Of Integer)()
Try
For i As Integer = 1 To pdf.GetPageCount()
pdf.SelectPage(i)
Dim frameId As Integer = pdf.RenderPageToGdPictureImage(200, False) ' new allocation
If frameId <> 0 Then frameIds.Add(frameId)
' ... OCR call here ...
Next
Finally
For Each id In frameIds
_imaging.ReleaseGdPictureImage(id) ' manual per-frame release
Next
End Try
End Using
IronOCR eliminates the lifecycle entirely. OcrInput handles frame enumeration and cleanup:
// IronOCR: the using statement handles everything
using var input = new OcrInput();
input.LoadImageFrames("multi-page.tiff");
var result = new IronTesseract().Read(input);
// IronOCR: the using statement handles everything
using var input = new OcrInput();
input.LoadImageFrames("multi-page.tiff");
var result = new IronTesseract().Read(input);
Imports IronOcr
Dim input As New OcrInput()
Using input
input.LoadImageFrames("multi-page.tiff")
Dim result = New IronTesseract().Read(input)
End Using
IronOCR vs GdPicture.NET: Feature Comparison
The table below maps feature coverage between the two libraries for OCR-focused workflows.
| Feature | GdPicture.NET | IronOCR |
|---|---|---|
| Installation | Multiple NuGet packages | dotnet add package IronOcr |
| License activation | LicenseManager.RegisterKEY() |
IronOcr.License.LicenseKey = "..." |
| Namespace on major upgrade | Requires find-and-replace (GdPicture14 → next) |
Unchanged (IronOcr) |
| Component initialization | GdPictureImaging + GdPictureOCR + GdPicturePDF |
new IronTesseract() |
| Resource memory model | Manual integer ID tracking and release | IDisposable / using statement |
| Memory leak risk | High (missing ReleaseGdPictureImage) |
None (compiler-enforced via using) |
| External resource folder | Required (_ocr.ResourceFolder = path) |
Not required — bundled in package |
| Image OCR | Yes | Yes |
| PDF OCR | Yes (PDF Plugin required) | Built-in, no additional license |
| Multi-page TIFF | Manual frame loop + per-frame ID cleanup | input.LoadImageFrames() |
| Stream input | Via GdPictureImaging stream overload |
input.LoadImage(stream) |
| Searchable PDF output | pdf.OcrPage() + pdf.SaveToFile() |
result.SaveAsSearchablePdf() |
| Deskew preprocessing | Document Imaging Plugin required | input.Deskew() — built-in |
| Noise removal | Document Imaging Plugin required | input.DeNoise() — built-in |
| Languages | Tesseract-based traineddata files in folder | 125+ via NuGet packages |
| Multi-language OCR | Yes | OcrLanguage.French + OcrLanguage.German |
| Thread safety | Manual instance management | Thread-safe by design |
| Async OCR | Not built-in | ReadAsync() |
| Barcode reading | Separate barcode plugin | ocr.Configuration.ReadBarCodes = true |
| Structured output | Index-based block/line/word access | Typed .Pages, .Words, .Characters |
| Confidence scoring | GetOCRResultConfidence(resultId) |
result.Confidence |
| Cross-platform | Windows, Linux, macOS | Windows, Linux, macOS, Docker, AWS, Azure |
| Entry cost (OCR from PDFs) | ~$8,000 (Core + OCR + PDF plugins) | $999–$2,999 |
| Pricing model | Plugin-based perpetual + 20%/yr maintenance | Flat perpetual, optional annual updates |
| Commercial support | Yes | Yes |
Quick Start: GdPicture.NET to IronOCR Migration
Step 1: Replace NuGet Package
Remove the GdPicture packages:
dotnet remove package GdPicture.NET
dotnet remove package GdPicture.NET.OCR
dotnet remove package GdPicture.NET.PDF
dotnet remove package GdPicture.NET
dotnet remove package GdPicture.NET.OCR
dotnet remove package GdPicture.NET.PDF
Install IronOCR from the NuGet package page:
dotnet add package IronOcr
For languages beyond English, install the corresponding language pack:
dotnet add package IronOcr.Languages.French
dotnet add package IronOcr.Languages.German
dotnet add package IronOcr.Languages.French
dotnet add package IronOcr.Languages.German
Step 2: Update Namespaces
Replace the GdPicture namespace with the IronOCR namespace:
// Before (GdPicture)
using GdPicture14;
// After (IronOCR)
using IronOcr;
// Before (GdPicture)
using GdPicture14;
// After (IronOCR)
using IronOcr;
Imports IronOcr
Step 3: Initialize License
Place this line in Program.cs or Startup.cs, before any OCR calls:
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Remove the GdPicture license registration block entirely:
// Remove this
LicenseManager lm = new LicenseManager();
lm.RegisterKEY("GDPICTURE-LICENSE-KEY");
// Remove this
LicenseManager lm = new LicenseManager();
lm.RegisterKEY("GDPICTURE-LICENSE-KEY");
A free trial key is available from the IronOCR product page to evaluate before purchasing.
Code Migration Examples
Multi-Page TIFF Frame Processing
Processing multi-page TIFF files in GdPicture requires selecting each frame through the PDF/imaging API, rendering it to a new image ID, running OCR, and releasing the ID. A single frame that is not released in the finally block leaks 10–50 MB depending on DPI.
GdPicture.NET Approach:
using GdPicture14;
public class GdPictureTiffProcessor
{
private readonly GdPictureImaging _imaging;
private readonly GdPictureOCR _ocr;
public string ExtractTextFromTiff(string tiffPath)
{
var text = new StringBuilder();
// Load TIFF through the imaging component
int tiffId = _imaging.CreateGdPictureImageFromFile(tiffPath);
if (tiffId == 0)
throw new Exception($"TIFF load failed: {_imaging.GetStat()}");
// Outer try: release the original TIFF handle
try
{
int frameCount = _imaging.GetPageCount(tiffId);
for (int i = 1; i <= frameCount; i++)
{
// Switch to frame — modifies the existing ID in place
_imaging.SelectPage(tiffId, i);
// Clone frame to a new image ID for OCR
int frameId = _imaging.CloneImage(tiffId);
if (frameId == 0) continue;
// Inner try: release each cloned frame ID
try
{
_ocr.SetImage(frameId);
_ocr.Language = "eng";
string resultId = _ocr.RunOCR();
if (!string.IsNullOrEmpty(resultId))
{
text.AppendLine($"[Frame {i}] {_ocr.GetOCRResultText(resultId)}");
}
}
finally
{
// Release cloned frame — critical for each iteration
_imaging.ReleaseGdPictureImage(frameId);
}
}
}
finally
{
// Release original TIFF handle
_imaging.ReleaseGdPictureImage(tiffId);
}
return text.ToString();
}
}
using GdPicture14;
public class GdPictureTiffProcessor
{
private readonly GdPictureImaging _imaging;
private readonly GdPictureOCR _ocr;
public string ExtractTextFromTiff(string tiffPath)
{
var text = new StringBuilder();
// Load TIFF through the imaging component
int tiffId = _imaging.CreateGdPictureImageFromFile(tiffPath);
if (tiffId == 0)
throw new Exception($"TIFF load failed: {_imaging.GetStat()}");
// Outer try: release the original TIFF handle
try
{
int frameCount = _imaging.GetPageCount(tiffId);
for (int i = 1; i <= frameCount; i++)
{
// Switch to frame — modifies the existing ID in place
_imaging.SelectPage(tiffId, i);
// Clone frame to a new image ID for OCR
int frameId = _imaging.CloneImage(tiffId);
if (frameId == 0) continue;
// Inner try: release each cloned frame ID
try
{
_ocr.SetImage(frameId);
_ocr.Language = "eng";
string resultId = _ocr.RunOCR();
if (!string.IsNullOrEmpty(resultId))
{
text.AppendLine($"[Frame {i}] {_ocr.GetOCRResultText(resultId)}");
}
}
finally
{
// Release cloned frame — critical for each iteration
_imaging.ReleaseGdPictureImage(frameId);
}
}
}
finally
{
// Release original TIFF handle
_imaging.ReleaseGdPictureImage(tiffId);
}
return text.ToString();
}
}
Imports GdPicture14
Imports System.Text
Public Class GdPictureTiffProcessor
Private ReadOnly _imaging As GdPictureImaging
Private ReadOnly _ocr As GdPictureOCR
Public Function ExtractTextFromTiff(tiffPath As String) As String
Dim text As New StringBuilder()
' Load TIFF through the imaging component
Dim tiffId As Integer = _imaging.CreateGdPictureImageFromFile(tiffPath)
If tiffId = 0 Then
Throw New Exception($"TIFF load failed: {_imaging.GetStat()}")
End If
' Outer try: release the original TIFF handle
Try
Dim frameCount As Integer = _imaging.GetPageCount(tiffId)
For i As Integer = 1 To frameCount
' Switch to frame — modifies the existing ID in place
_imaging.SelectPage(tiffId, i)
' Clone frame to a new image ID for OCR
Dim frameId As Integer = _imaging.CloneImage(tiffId)
If frameId = 0 Then Continue For
' Inner try: release each cloned frame ID
Try
_ocr.SetImage(frameId)
_ocr.Language = "eng"
Dim resultId As String = _ocr.RunOCR()
If Not String.IsNullOrEmpty(resultId) Then
text.AppendLine($"[Frame {i}] {_ocr.GetOCRResultText(resultId)}")
End If
Finally
' Release cloned frame — critical for each iteration
_imaging.ReleaseGdPictureImage(frameId)
End Try
Next
Finally
' Release original TIFF handle
_imaging.ReleaseGdPictureImage(tiffId)
End Try
Return text.ToString()
End Function
End Class
IronOCR Approach:
using IronOcr;
public class IronOcrTiffProcessor
{
public string ExtractTextFromTiff(string tiffPath)
{
using var input = new OcrInput();
input.LoadImageFrames(tiffPath); // all frames loaded, all cleanup automatic
var result = new IronTesseract().Read(input);
// Per-frame text is available on result.Pages
foreach (var page in result.Pages)
Console.WriteLine($"[Frame {page.PageNumber}] {page.Text}");
return result.Text;
}
}
using IronOcr;
public class IronOcrTiffProcessor
{
public string ExtractTextFromTiff(string tiffPath)
{
using var input = new OcrInput();
input.LoadImageFrames(tiffPath); // all frames loaded, all cleanup automatic
var result = new IronTesseract().Read(input);
// Per-frame text is available on result.Pages
foreach (var page in result.Pages)
Console.WriteLine($"[Frame {page.PageNumber}] {page.Text}");
return result.Text;
}
}
Imports IronOcr
Public Class IronOcrTiffProcessor
Public Function ExtractTextFromTiff(tiffPath As String) As String
Using input As New OcrInput()
input.LoadImageFrames(tiffPath) ' all frames loaded, all cleanup automatic
Dim result = New IronTesseract().Read(input)
' Per-frame text is available on result.Pages
For Each page In result.Pages
Console.WriteLine($"[Frame {page.PageNumber}] {page.Text}")
Next
Return result.Text
End Using
End Function
End Class
LoadImageFrames loads every frame from a multi-page TIFF into the OcrInput pipeline. The using block handles all memory associated with every frame at the end of the scope. No List<int> to maintain, no nested try/finally blocks required. The TIFF and GIF input guide covers frame selection and multi-format handling in detail.
Stream-Based Input Replacing Plugin Initialization
Server applications frequently receive documents as streams rather than file paths — from HTTP uploads, message queues, or database blobs. GdPicture requires loading the stream through GdPictureImaging, which itself requires the component initialization sequence and resource folder configuration that precede every operation.
GdPicture.NET Approach:
using GdPicture14;
public class GdPictureStreamOcr : IDisposable
{
private readonly GdPictureImaging _imaging;
private readonly GdPictureOCR _ocr;
public GdPictureStreamOcr()
{
// Plugin initialization required before stream loading is possible
var lm = new LicenseManager();
lm.RegisterKEY("GDPICTURE-LICENSE-KEY");
_imaging = new GdPictureImaging();
_ocr = new GdPictureOCR();
_ocr.ResourceFolder = @"C:\GdPicture\Resources\OCR"; // path must exist at runtime
}
public string ExtractTextFromStream(Stream documentStream)
{
// Load stream into imaging component to get an image ID
int imageId = _imaging.CreateGdPictureImageFromStream(documentStream);
if (imageId == 0)
throw new Exception($"Stream load failed: {_imaging.GetStat()}");
try
{
_ocr.SetImage(imageId);
_ocr.Language = "eng";
string resultId = _ocr.RunOCR();
if (string.IsNullOrEmpty(resultId))
throw new Exception($"OCR failed: {_ocr.GetStat()}");
return _ocr.GetOCRResultText(resultId);
}
finally
{
_imaging.ReleaseGdPictureImage(imageId);
}
}
public void Dispose()
{
_ocr?.Dispose();
_imaging?.Dispose();
}
}
using GdPicture14;
public class GdPictureStreamOcr : IDisposable
{
private readonly GdPictureImaging _imaging;
private readonly GdPictureOCR _ocr;
public GdPictureStreamOcr()
{
// Plugin initialization required before stream loading is possible
var lm = new LicenseManager();
lm.RegisterKEY("GDPICTURE-LICENSE-KEY");
_imaging = new GdPictureImaging();
_ocr = new GdPictureOCR();
_ocr.ResourceFolder = @"C:\GdPicture\Resources\OCR"; // path must exist at runtime
}
public string ExtractTextFromStream(Stream documentStream)
{
// Load stream into imaging component to get an image ID
int imageId = _imaging.CreateGdPictureImageFromStream(documentStream);
if (imageId == 0)
throw new Exception($"Stream load failed: {_imaging.GetStat()}");
try
{
_ocr.SetImage(imageId);
_ocr.Language = "eng";
string resultId = _ocr.RunOCR();
if (string.IsNullOrEmpty(resultId))
throw new Exception($"OCR failed: {_ocr.GetStat()}");
return _ocr.GetOCRResultText(resultId);
}
finally
{
_imaging.ReleaseGdPictureImage(imageId);
}
}
public void Dispose()
{
_ocr?.Dispose();
_imaging?.Dispose();
}
}
IRON VB CONVERTER ERROR developers@ironsoftware.com
IronOCR Approach:
using IronOcr;
public class IronOcrStreamOcr
{
public string ExtractTextFromStream(Stream documentStream)
{
using var input = new OcrInput();
input.LoadImage(documentStream); // stream accepted directly — no imaging component
return new IronTesseract().Read(input).Text;
}
}
using IronOcr;
public class IronOcrStreamOcr
{
public string ExtractTextFromStream(Stream documentStream)
{
using var input = new OcrInput();
input.LoadImage(documentStream); // stream accepted directly — no imaging component
return new IronTesseract().Read(input).Text;
}
}
Imports IronOcr
Public Class IronOcrStreamOcr
Public Function ExtractTextFromStream(documentStream As Stream) As String
Using input As New OcrInput()
input.LoadImage(documentStream) ' stream accepted directly — no imaging component
Return New IronTesseract().Read(input).Text
End Using
End Function
End Class
The GdPicture approach requires constructing three objects and configuring a filesystem path before the first stream can be consumed. IronOCR accepts the stream directly on OcrInput with no prior setup. The stream input guide covers byte array, MemoryStream, and FileStream patterns for pipeline architectures where temporary file writes are undesirable.
Async OCR Replacing Status-Code Polling
GdPicture OCR is synchronous. Applications that process documents in ASP.NET Core endpoints or background services must wrap RunOCR in Task.Run to avoid blocking request threads — and then manage the image ID lifecycle across the thread boundary. IronOCR provides first-class async support through ReadAsync.
GdPicture.NET Approach:
using GdPicture14;
using System.Threading.Tasks;
public class GdPictureAsyncWrapper
{
private readonly GdPictureImaging _imaging;
private readonly GdPictureOCR _ocr;
private readonly SemaphoreSlim _lock = new SemaphoreSlim(1, 1);
public async Task<string> ExtractTextAsync(string imagePath)
{
// Must acquire lock: GdPictureOCR is not thread-safe
await _lock.WaitAsync();
try
{
return await Task.Run(() =>
{
int imageId = _imaging.CreateGdPictureImageFromFile(imagePath);
if (imageId == 0)
throw new Exception($"Load failed: {_imaging.GetStat()}");
try
{
_ocr.SetImage(imageId);
_ocr.Language = "eng";
string resultId = _ocr.RunOCR();
if (string.IsNullOrEmpty(resultId))
throw new Exception($"OCR failed: {_ocr.GetStat()}");
return _ocr.GetOCRResultText(resultId);
}
finally
{
_imaging.ReleaseGdPictureImage(imageId);
}
});
}
finally
{
_lock.Release();
}
}
}
using GdPicture14;
using System.Threading.Tasks;
public class GdPictureAsyncWrapper
{
private readonly GdPictureImaging _imaging;
private readonly GdPictureOCR _ocr;
private readonly SemaphoreSlim _lock = new SemaphoreSlim(1, 1);
public async Task<string> ExtractTextAsync(string imagePath)
{
// Must acquire lock: GdPictureOCR is not thread-safe
await _lock.WaitAsync();
try
{
return await Task.Run(() =>
{
int imageId = _imaging.CreateGdPictureImageFromFile(imagePath);
if (imageId == 0)
throw new Exception($"Load failed: {_imaging.GetStat()}");
try
{
_ocr.SetImage(imageId);
_ocr.Language = "eng";
string resultId = _ocr.RunOCR();
if (string.IsNullOrEmpty(resultId))
throw new Exception($"OCR failed: {_ocr.GetStat()}");
return _ocr.GetOCRResultText(resultId);
}
finally
{
_imaging.ReleaseGdPictureImage(imageId);
}
});
}
finally
{
_lock.Release();
}
}
}
Imports GdPicture14
Imports System.Threading.Tasks
Public Class GdPictureAsyncWrapper
Private ReadOnly _imaging As GdPictureImaging
Private ReadOnly _ocr As GdPictureOCR
Private ReadOnly _lock As New SemaphoreSlim(1, 1)
Public Async Function ExtractTextAsync(imagePath As String) As Task(Of String)
' Must acquire lock: GdPictureOCR is not thread-safe
Await _lock.WaitAsync()
Try
Return Await Task.Run(Function()
Dim imageId As Integer = _imaging.CreateGdPictureImageFromFile(imagePath)
If imageId = 0 Then
Throw New Exception($"Load failed: {_imaging.GetStat()}")
End If
Try
_ocr.SetImage(imageId)
_ocr.Language = "eng"
Dim resultId As String = _ocr.RunOCR()
If String.IsNullOrEmpty(resultId) Then
Throw New Exception($"OCR failed: {_ocr.GetStat()}")
End If
Return _ocr.GetOCRResultText(resultId)
Finally
_imaging.ReleaseGdPictureImage(imageId)
End Try
End Function)
Finally
_lock.Release()
End Try
End Function
End Class
IronOCR Approach:
using IronOcr;
public class IronOcrAsyncService
{
private readonly IronTesseract _ocr = new IronTesseract();
public async Task<string> ExtractTextAsync(string imagePath)
{
// ReadAsync is natively async — no Task.Run wrapper, no lock required
var result = await _ocr.ReadAsync(imagePath);
return result.Text;
}
public async Task<string> ExtractFromStreamAsync(Stream stream)
{
using var input = new OcrInput();
input.LoadImage(stream);
var result = await _ocr.ReadAsync(input);
return result.Text;
}
}
using IronOcr;
public class IronOcrAsyncService
{
private readonly IronTesseract _ocr = new IronTesseract();
public async Task<string> ExtractTextAsync(string imagePath)
{
// ReadAsync is natively async — no Task.Run wrapper, no lock required
var result = await _ocr.ReadAsync(imagePath);
return result.Text;
}
public async Task<string> ExtractFromStreamAsync(Stream stream)
{
using var input = new OcrInput();
input.LoadImage(stream);
var result = await _ocr.ReadAsync(input);
return result.Text;
}
}
Imports IronOcr
Imports System.IO
Imports System.Threading.Tasks
Public Class IronOcrAsyncService
Private ReadOnly _ocr As New IronTesseract()
Public Async Function ExtractTextAsync(imagePath As String) As Task(Of String)
' ReadAsync is natively async — no Task.Run wrapper, no lock required
Dim result = Await _ocr.ReadAsync(imagePath)
Return result.Text
End Function
Public Async Function ExtractFromStreamAsync(stream As Stream) As Task(Of String)
Using input As New OcrInput()
input.LoadImage(stream)
Dim result = Await _ocr.ReadAsync(input)
Return result.Text
End Using
End Function
End Class
The GdPicture approach requires a SemaphoreSlim to serialize access to the shared GdPictureOCR instance, plus a Task.Run to move synchronous blocking work off the calling thread, plus the full image ID lifecycle inside that lambda. IronOCR's ReadAsync is genuinely non-blocking and thread-safe. The async OCR guide covers integration with ASP.NET Core middleware and hosted background services.
Parallel Batch Processing with Thread Safety
Processing a folder of scanned documents as fast as possible requires parallel execution. GdPicture requires one GdPictureOCR instance per thread — sharing a single instance causes non-deterministic failures. Each per-thread instance also requires its own GdPictureImaging component and resource folder configuration, making thread pool approaches impractical without a factory pattern.
GdPicture.NET Approach:
using GdPicture14;
using System.Collections.Concurrent;
using System.Threading.Tasks;
public class GdPictureParallelBatch
{
private readonly string _resourceFolder = @"C:\GdPicture\Resources\OCR";
public ConcurrentDictionary<string, string> ProcessBatch(string[] imagePaths)
{
var results = new ConcurrentDictionary<string, string>();
// Each thread must have its own component instances
Parallel.ForEach(imagePaths, imagePath =>
{
// Create per-thread instances — shared instances cause failures
using var threadImaging = new GdPictureImaging();
using var threadOcr = new GdPictureOCR();
threadOcr.ResourceFolder = _resourceFolder;
// Re-register license per thread (may be required depending on SDK version)
var lm = new LicenseManager();
lm.RegisterKEY("GDPICTURE-LICENSE-KEY");
int imageId = threadImaging.CreateGdPictureImageFromFile(imagePath);
if (imageId == 0)
{
results[imagePath] = $"ERROR: {threadImaging.GetStat()}";
return;
}
try
{
threadOcr.SetImage(imageId);
threadOcr.Language = "eng";
string resultId = threadOcr.RunOCR();
results[imagePath] = string.IsNullOrEmpty(resultId)
? $"ERROR: {threadOcr.GetStat()}"
: threadOcr.GetOCRResultText(resultId);
}
finally
{
threadImaging.ReleaseGdPictureImage(imageId);
}
});
return results;
}
}
using GdPicture14;
using System.Collections.Concurrent;
using System.Threading.Tasks;
public class GdPictureParallelBatch
{
private readonly string _resourceFolder = @"C:\GdPicture\Resources\OCR";
public ConcurrentDictionary<string, string> ProcessBatch(string[] imagePaths)
{
var results = new ConcurrentDictionary<string, string>();
// Each thread must have its own component instances
Parallel.ForEach(imagePaths, imagePath =>
{
// Create per-thread instances — shared instances cause failures
using var threadImaging = new GdPictureImaging();
using var threadOcr = new GdPictureOCR();
threadOcr.ResourceFolder = _resourceFolder;
// Re-register license per thread (may be required depending on SDK version)
var lm = new LicenseManager();
lm.RegisterKEY("GDPICTURE-LICENSE-KEY");
int imageId = threadImaging.CreateGdPictureImageFromFile(imagePath);
if (imageId == 0)
{
results[imagePath] = $"ERROR: {threadImaging.GetStat()}";
return;
}
try
{
threadOcr.SetImage(imageId);
threadOcr.Language = "eng";
string resultId = threadOcr.RunOCR();
results[imagePath] = string.IsNullOrEmpty(resultId)
? $"ERROR: {threadOcr.GetStat()}"
: threadOcr.GetOCRResultText(resultId);
}
finally
{
threadImaging.ReleaseGdPictureImage(imageId);
}
});
return results;
}
}
Imports GdPicture14
Imports System.Collections.Concurrent
Imports System.Threading.Tasks
Public Class GdPictureParallelBatch
Private ReadOnly _resourceFolder As String = "C:\GdPicture\Resources\OCR"
Public Function ProcessBatch(imagePaths As String()) As ConcurrentDictionary(Of String, String)
Dim results As New ConcurrentDictionary(Of String, String)()
' Each thread must have its own component instances
Parallel.ForEach(imagePaths, Sub(imagePath)
' Create per-thread instances — shared instances cause failures
Using threadImaging As New GdPictureImaging()
Using threadOcr As New GdPictureOCR()
threadOcr.ResourceFolder = _resourceFolder
' Re-register license per thread (may be required depending on SDK version)
Dim lm As New LicenseManager()
lm.RegisterKEY("GDPICTURE-LICENSE-KEY")
Dim imageId As Integer = threadImaging.CreateGdPictureImageFromFile(imagePath)
If imageId = 0 Then
results(imagePath) = $"ERROR: {threadImaging.GetStat()}"
Return
End If
Try
threadOcr.SetImage(imageId)
threadOcr.Language = "eng"
Dim resultId As String = threadOcr.RunOCR()
results(imagePath) = If(String.IsNullOrEmpty(resultId), $"ERROR: {threadOcr.GetStat()}", threadOcr.GetOCRResultText(resultId))
Finally
threadImaging.ReleaseGdPictureImage(imageId)
End Try
End Using
End Using
End Sub)
Return results
End Function
End Class
IronOCR Approach:
using IronOcr;
using System.Collections.Concurrent;
using System.Threading.Tasks;
public class IronOcrParallelBatch
{
public ConcurrentDictionary<string, string> ProcessBatch(string[] imagePaths)
{
var results = new ConcurrentDictionary<string, string>();
// IronTesseract is thread-safe — one instance handles all threads
var ocr = new IronTesseract();
Parallel.ForEach(imagePaths, imagePath =>
{
try
{
results[imagePath] = ocr.Read(imagePath).Text;
}
catch (Exception ex)
{
results[imagePath] = $"ERROR: {ex.Message}";
}
});
return results;
}
}
using IronOcr;
using System.Collections.Concurrent;
using System.Threading.Tasks;
public class IronOcrParallelBatch
{
public ConcurrentDictionary<string, string> ProcessBatch(string[] imagePaths)
{
var results = new ConcurrentDictionary<string, string>();
// IronTesseract is thread-safe — one instance handles all threads
var ocr = new IronTesseract();
Parallel.ForEach(imagePaths, imagePath =>
{
try
{
results[imagePath] = ocr.Read(imagePath).Text;
}
catch (Exception ex)
{
results[imagePath] = $"ERROR: {ex.Message}";
}
});
return results;
}
}
Imports IronOcr
Imports System.Collections.Concurrent
Imports System.Threading.Tasks
Public Class IronOcrParallelBatch
Public Function ProcessBatch(imagePaths As String()) As ConcurrentDictionary(Of String, String)
Dim results As New ConcurrentDictionary(Of String, String)()
' IronTesseract is thread-safe — one instance handles all threads
Dim ocr As New IronTesseract()
Parallel.ForEach(imagePaths, Sub(imagePath)
Try
results(imagePath) = ocr.Read(imagePath).Text
Catch ex As Exception
results(imagePath) = $"ERROR: {ex.Message}"
End Try
End Sub)
Return results
End Function
End Class
The GdPicture parallel implementation creates three objects per thread and requires per-thread license registration. IronOCR handles concurrent reads from a single IronTesseract instance with no synchronization overhead. The multithreading example demonstrates throughput benchmarks and Parallel.ForEach patterns for high-volume document processing workloads.
Byte Array Input and Structured Paragraph Extraction
Applications that retrieve documents from databases or object storage often work with byte arrays rather than file paths. GdPicture requires converting the byte array to a stream and loading it through GdPictureImaging. Extracting structured paragraph-level data from the result requires navigating the block/line index hierarchy.
GdPicture.NET Approach:
using GdPicture14;
public class GdPictureByteArrayOcr
{
private readonly GdPictureImaging _imaging;
private readonly GdPictureOCR _ocr;
public List<string> ExtractParagraphsFromBytes(byte[] imageBytes)
{
var paragraphs = new List<string>();
// Byte array must go through MemoryStream to reach CreateGdPictureImageFromStream
using var ms = new MemoryStream(imageBytes);
int imageId = _imaging.CreateGdPictureImageFromStream(ms);
if (imageId == 0)
throw new Exception($"Byte array load failed: {_imaging.GetStat()}");
try
{
_ocr.SetImage(imageId);
_ocr.Language = "eng";
string resultId = _ocr.RunOCR();
if (string.IsNullOrEmpty(resultId))
throw new Exception($"OCR failed: {_ocr.GetStat()}");
// Paragraph-level data requires iterating block structure
int blockCount = _ocr.GetOCRResultBlockCount(resultId);
for (int b = 0; b < blockCount; b++)
{
var blockText = new StringBuilder();
int lineCount = _ocr.GetOCRResultBlockLineCount(resultId, b);
for (int l = 0; l < lineCount; l++)
{
int wordCount = _ocr.GetOCRResultBlockLineWordCount(resultId, b, l);
for (int w = 0; w < wordCount; w++)
{
blockText.Append(_ocr.GetOCRResultBlockLineWordText(resultId, b, l, w));
blockText.Append(" ");
}
}
string text = blockText.ToString().Trim();
if (!string.IsNullOrEmpty(text))
paragraphs.Add(text);
}
}
finally
{
_imaging.ReleaseGdPictureImage(imageId);
}
return paragraphs;
}
}
using GdPicture14;
public class GdPictureByteArrayOcr
{
private readonly GdPictureImaging _imaging;
private readonly GdPictureOCR _ocr;
public List<string> ExtractParagraphsFromBytes(byte[] imageBytes)
{
var paragraphs = new List<string>();
// Byte array must go through MemoryStream to reach CreateGdPictureImageFromStream
using var ms = new MemoryStream(imageBytes);
int imageId = _imaging.CreateGdPictureImageFromStream(ms);
if (imageId == 0)
throw new Exception($"Byte array load failed: {_imaging.GetStat()}");
try
{
_ocr.SetImage(imageId);
_ocr.Language = "eng";
string resultId = _ocr.RunOCR();
if (string.IsNullOrEmpty(resultId))
throw new Exception($"OCR failed: {_ocr.GetStat()}");
// Paragraph-level data requires iterating block structure
int blockCount = _ocr.GetOCRResultBlockCount(resultId);
for (int b = 0; b < blockCount; b++)
{
var blockText = new StringBuilder();
int lineCount = _ocr.GetOCRResultBlockLineCount(resultId, b);
for (int l = 0; l < lineCount; l++)
{
int wordCount = _ocr.GetOCRResultBlockLineWordCount(resultId, b, l);
for (int w = 0; w < wordCount; w++)
{
blockText.Append(_ocr.GetOCRResultBlockLineWordText(resultId, b, l, w));
blockText.Append(" ");
}
}
string text = blockText.ToString().Trim();
if (!string.IsNullOrEmpty(text))
paragraphs.Add(text);
}
}
finally
{
_imaging.ReleaseGdPictureImage(imageId);
}
return paragraphs;
}
}
Imports GdPicture14
Imports System.IO
Imports System.Text
Public Class GdPictureByteArrayOcr
Private ReadOnly _imaging As GdPictureImaging
Private ReadOnly _ocr As GdPictureOCR
Public Function ExtractParagraphsFromBytes(imageBytes As Byte()) As List(Of String)
Dim paragraphs As New List(Of String)()
' Byte array must go through MemoryStream to reach CreateGdPictureImageFromStream
Using ms As New MemoryStream(imageBytes)
Dim imageId As Integer = _imaging.CreateGdPictureImageFromStream(ms)
If imageId = 0 Then
Throw New Exception($"Byte array load failed: {_imaging.GetStat()}")
End If
Try
_ocr.SetImage(imageId)
_ocr.Language = "eng"
Dim resultId As String = _ocr.RunOCR()
If String.IsNullOrEmpty(resultId) Then
Throw New Exception($"OCR failed: {_ocr.GetStat()}")
End If
' Paragraph-level data requires iterating block structure
Dim blockCount As Integer = _ocr.GetOCRResultBlockCount(resultId)
For b As Integer = 0 To blockCount - 1
Dim blockText As New StringBuilder()
Dim lineCount As Integer = _ocr.GetOCRResultBlockLineCount(resultId, b)
For l As Integer = 0 To lineCount - 1
Dim wordCount As Integer = _ocr.GetOCRResultBlockLineWordCount(resultId, b, l)
For w As Integer = 0 To wordCount - 1
blockText.Append(_ocr.GetOCRResultBlockLineWordText(resultId, b, l, w))
blockText.Append(" "c)
Next
Next
Dim text As String = blockText.ToString().Trim()
If Not String.IsNullOrEmpty(text) Then
paragraphs.Add(text)
End If
Next
Finally
_imaging.ReleaseGdPictureImage(imageId)
End Try
End Using
Return paragraphs
End Function
End Class
IronOCR Approach:
using IronOcr;
public class IronOcrByteArrayOcr
{
public List<string> ExtractParagraphsFromBytes(byte[] imageBytes)
{
using var input = new OcrInput();
input.LoadImage(imageBytes); // byte array accepted directly
var result = new IronTesseract().Read(input);
// Paragraphs are a first-class typed collection
return result.Paragraphs
.Select(p => p.Text)
.Where(t => !string.IsNullOrWhiteSpace(t))
.ToList();
}
}
using IronOcr;
public class IronOcrByteArrayOcr
{
public List<string> ExtractParagraphsFromBytes(byte[] imageBytes)
{
using var input = new OcrInput();
input.LoadImage(imageBytes); // byte array accepted directly
var result = new IronTesseract().Read(input);
// Paragraphs are a first-class typed collection
return result.Paragraphs
.Select(p => p.Text)
.Where(t => !string.IsNullOrWhiteSpace(t))
.ToList();
}
}
Imports IronOcr
Public Class IronOcrByteArrayOcr
Public Function ExtractParagraphsFromBytes(imageBytes As Byte()) As List(Of String)
Using input As New OcrInput()
input.LoadImage(imageBytes) ' byte array accepted directly
Dim result = New IronTesseract().Read(input)
' Paragraphs are a first-class typed collection
Return result.Paragraphs _
.Select(Function(p) p.Text) _
.Where(Function(t) Not String.IsNullOrWhiteSpace(t)) _
.ToList()
End Using
End Function
End Class
IronOCR accepts byte[] directly on LoadImage without the intermediate MemoryStream. The result exposes .Paragraphs as a typed LINQ-queryable collection — no block/line/word triple-loop required. The read results guide covers coordinate access, confidence filtering, and structured output patterns for invoice and form processing workflows. For scanning specific document areas, see region-based OCR.
GdPicture.NET API to IronOCR Mapping Reference
| GdPicture.NET | IronOCR Equivalent |
|---|---|
using GdPicture14; |
using IronOcr; |
LicenseManager.RegisterKEY("key") |
IronOcr.License.LicenseKey = "key" |
new GdPictureImaging() |
Not required — internal to OcrInput |
new GdPictureOCR() |
new IronTesseract() |
new GdPicturePDF() |
Not required — OcrInput.LoadPdf() handles this |
_ocr.ResourceFolder = path |
Not required — resources bundled in NuGet |
imaging.CreateGdPictureImageFromFile(path) |
input.LoadImage(path) |
imaging.CreateGdPictureImageFromStream(stream) |
input.LoadImage(stream) |
imaging.CreateGdPictureImageFromBytes(bytes) |
input.LoadImage(bytes) |
imaging.CloneImage(tiffId) per frame |
input.LoadImageFrames(tiffPath) |
imaging.ReleaseGdPictureImage(imageId) |
using var input = new OcrInput() — automatic |
ocr.SetImage(imageId) |
Not required — OcrInput holds the image |
ocr.Language = "eng" |
ocr.Language = OcrLanguage.English |
ocr.RunOCR() → resultId string |
ocr.Read(input) → typed OcrResult |
ocr.GetOCRResultText(resultId) |
result.Text |
ocr.GetOCRResultConfidence(resultId) |
result.Confidence |
ocr.GetOCRResultBlockCount(resultId) |
result.Pages[i].Paragraphs.Count |
ocr.GetOCRResultBlockLineWordText(resultId, b, l, w) |
result.Words[i].Text |
imaging.GetStat() / ocr.GetStat() |
Standard .NET exceptions |
GdPictureStatus.OK check after each call |
Not required — exceptions propagate |
pdf.OcrPage("eng", resourcePath, "", 200) |
result.SaveAsSearchablePdf(outputPath) |
pdf.RenderPageToGdPictureImage(200, false) |
Not required — IronOCR renders internally |
pdf.SelectPage(i) |
Not required — all pages processed by default |
pdf.GetPageCount() |
result.Pages.Count |
Task.Run(() => ocr.RunOCR()) + SemaphoreSlim |
await ocr.ReadAsync(input) |
Common Migration Issues and Solutions
Issue 1: Image ID Variables Left in Code After Refactor
GdPicture.NET: Existing code declares int imageId at the top of methods, tracks it through try/finally, and passes it to multiple GdPicture calls. After replacing GdPicture calls, these variables and their ReleaseGdPictureImage calls become orphaned dead code.
Solution: Delete the entire image ID pattern. Replace the declaration, the try, the finally, and the release call with a using var input = new OcrInput() block. Grep for ReleaseGdPictureImage to find every cleanup call that must be removed:
grep -rn "ReleaseGdPictureImage\|imageId\|resultId" --include="*.cs" .
grep -rn "ReleaseGdPictureImage\|imageId\|resultId" --include="*.cs" .
// Remove all of this
int imageId = _imaging.CreateGdPictureImageFromFile(path);
try
{
_ocr.SetImage(imageId);
string resultId = _ocr.RunOCR();
return _ocr.GetOCRResultText(resultId);
}
finally
{
_imaging.ReleaseGdPictureImage(imageId);
}
// Replace with
using var input = new OcrInput();
input.LoadImage(path);
return new IronTesseract().Read(input).Text;
// Remove all of this
int imageId = _imaging.CreateGdPictureImageFromFile(path);
try
{
_ocr.SetImage(imageId);
string resultId = _ocr.RunOCR();
return _ocr.GetOCRResultText(resultId);
}
finally
{
_imaging.ReleaseGdPictureImage(imageId);
}
// Replace with
using var input = new OcrInput();
input.LoadImage(path);
return new IronTesseract().Read(input).Text;
' Remove all of this
Dim imageId As Integer = _imaging.CreateGdPictureImageFromFile(path)
Try
_ocr.SetImage(imageId)
Dim resultId As String = _ocr.RunOCR()
Return _ocr.GetOCRResultText(resultId)
Finally
_imaging.ReleaseGdPictureImage(imageId)
End Try
' Replace with
Using input As New OcrInput()
input.LoadImage(path)
Return New IronTesseract().Read(input).Text
End Using
Issue 2: ResourceFolder Path Not Found in Deployed Environment
GdPicture.NET: The path set in _ocr.ResourceFolder resolves on the development machine but fails in production. Common symptoms are silent OCR failures returning empty results, or generic GdPictureStatus errors that do not name the missing file.
Solution: Remove the ResourceFolder assignment entirely. English support is embedded in the IronOCR NuGet package. Additional languages install as NuGet packages. There is no filesystem path to configure or deploy:
# Remove the filesystem folder from deployment artifacts
# Add language NuGet packages to the project instead
dotnet add package IronOcr.Languages.French
dotnet add package IronOcr.Languages.German
# Remove the filesystem folder from deployment artifacts
# Add language NuGet packages to the project instead
dotnet add package IronOcr.Languages.French
dotnet add package IronOcr.Languages.German
Issue 3: GdPictureStatus Error Handling Replaced by Exceptions
GdPicture.NET: Every operation returns or sets a GdPictureStatus enum value that must be checked immediately. Code is dense with if (status != GdPictureStatus.OK) guards. Some status codes are generic (e.g., Error, InvalidParameter) and require consulting documentation to determine root cause.
Solution: IronOCR throws typed .NET exceptions. Replace status checks with try/catch blocks. Standard IOException and FileNotFoundException cover input failures; IronOcr.Exceptions.OcrException covers OCR-specific errors. See the IronTesseract setup guide for recommended error handling patterns:
try
{
var result = new IronTesseract().Read(imagePath);
return result.Text;
}
catch (FileNotFoundException ex)
{
_logger.LogError("Input file missing: {Path}", ex.FileName);
throw;
}
catch (IronOcr.Exceptions.OcrException ex)
{
_logger.LogError("OCR processing failed: {Message}", ex.Message);
throw;
}
try
{
var result = new IronTesseract().Read(imagePath);
return result.Text;
}
catch (FileNotFoundException ex)
{
_logger.LogError("Input file missing: {Path}", ex.FileName);
throw;
}
catch (IronOcr.Exceptions.OcrException ex)
{
_logger.LogError("OCR processing failed: {Message}", ex.Message);
throw;
}
Imports IronOcr
Imports System.IO
Try
Dim result = New IronTesseract().Read(imagePath)
Return result.Text
Catch ex As FileNotFoundException
_logger.LogError("Input file missing: {Path}", ex.FileName)
Throw
Catch ex As IronOcr.Exceptions.OcrException
_logger.LogError("OCR processing failed: {Message}", ex.Message)
Throw
End Try
Issue 4: GdPicture14 Namespace in Multiple Files
GdPicture.NET: The version number in the namespace means a project-wide using GdPicture14; directive exists in dozens of files. After migration, these must all be replaced with using IronOcr;.
Solution: Use a global find-and-replace across all .cs files, then verify no GdPicture references remain:
# Find all files with GdPicture namespace
grep -rln "using GdPicture" --include="*.cs" .
# After replacing, verify nothing remains
grep -rn "GdPicture14\|GdPictureOCR\|GdPictureImaging\|GdPicturePDF" --include="*.cs" .
# Find all files with GdPicture namespace
grep -rln "using GdPicture" --include="*.cs" .
# After replacing, verify nothing remains
grep -rn "GdPicture14\|GdPictureOCR\|GdPictureImaging\|GdPicturePDF" --include="*.cs" .
Issue 5: TIFF Frame Count Logic
GdPicture.NET: Code that iterates TIFF frames often mixes calls across GdPictureImaging.GetPageCount(imageId) and GdPicturePDF.GetPageCount() depending on how the file was loaded. The frame index is 1-based.
Solution: input.LoadImageFrames(path) handles all frames automatically. If your existing code only processes specific frames, use input.LoadImageFrames(path, frameNumbers) with a zero-based index array. Access per-frame results through result.Pages, which is also zero-indexed. The TIFF input guide documents the index behavior explicitly.
Issue 6: Preprocessing Requires Document Imaging Plugin
GdPicture.NET: Deskew and despeckle operations belong to the GdPictureDocumentImaging plugin, which requires a separate license purchase. Teams that skipped the plugin often have OCR accuracy problems on scanned documents with skewed or noisy pages.
Solution: IronOCR preprocessing methods are part of the base package. Add Deskew() and DeNoise() directly on OcrInput. The image quality correction guide covers all available filters including Contrast(), Sharpen(), Binarize(), and DeepCleanBackgroundNoise() for severely degraded scans:
using var input = new OcrInput();
input.LoadImage("scanned-document.tiff");
input.Deskew(); // no separate plugin license
input.DeNoise();
input.Contrast();
var result = new IronTesseract().Read(input);
using var input = new OcrInput();
input.LoadImage("scanned-document.tiff");
input.Deskew(); // no separate plugin license
input.DeNoise();
input.Contrast();
var result = new IronTesseract().Read(input);
Imports IronOcr
Using input As New OcrInput()
input.LoadImage("scanned-document.tiff")
input.Deskew() ' no separate plugin license
input.DeNoise()
input.Contrast()
Dim result = New IronTesseract().Read(input)
End Using
GdPicture.NET Migration Checklist
Pre-Migration
Audit the codebase to locate every GdPicture dependency before making changes:
# Find all GdPicture namespace imports
grep -rn "using GdPicture" --include="*.cs" .
# Find all image ID creation points
grep -rn "CreateGdPictureImageFromFile\|CreateGdPictureImageFromStream\|RenderPageToGdPictureImage\|CloneImage" --include="*.cs" .
# Find all release calls — these map to using block boundaries
grep -rn "ReleaseGdPictureImage" --include="*.cs" .
# Find all resource folder assignments
grep -rn "ResourceFolder" --include="*.cs" .
# Find all OCR result ID accesses
grep -rn "RunOCR\|GetOCRResult\|resultId" --include="*.cs" .
# Find all GdPictureStatus checks
grep -rn "GdPictureStatus\|GetStat()" --include="*.cs" .
# Count GdPicture-dependent files
grep -rln "GdPicture14" --include="*.cs" . | wc -l
# Find all GdPicture namespace imports
grep -rn "using GdPicture" --include="*.cs" .
# Find all image ID creation points
grep -rn "CreateGdPictureImageFromFile\|CreateGdPictureImageFromStream\|RenderPageToGdPictureImage\|CloneImage" --include="*.cs" .
# Find all release calls — these map to using block boundaries
grep -rn "ReleaseGdPictureImage" --include="*.cs" .
# Find all resource folder assignments
grep -rn "ResourceFolder" --include="*.cs" .
# Find all OCR result ID accesses
grep -rn "RunOCR\|GetOCRResult\|resultId" --include="*.cs" .
# Find all GdPictureStatus checks
grep -rn "GdPictureStatus\|GetStat()" --include="*.cs" .
# Count GdPicture-dependent files
grep -rln "GdPicture14" --include="*.cs" . | wc -l
Document the following before modifying code:
- Which files contain
GdPictureImaging,GdPictureOCR, andGdPicturePDFreferences - How many distinct image ID creation/release pairs exist
- Whether the Document Imaging Plugin (deskew, despeckle) is in use
- Which language
traineddatafiles are in the resource folder - Which deployment scripts or Docker files reference the resource folder path
Code Migration
- Remove all GdPicture NuGet packages from the project file
- Install
IronOcrvia NuGet - Install
IronOcr.Languages.*packages for each language previously in the resource folder - Add
IronOcr.License.LicenseKey = "..."toProgram.csorStartup.cs - Remove the
LicenseManager.RegisterKEY()call and the license manager object - Remove all
GdPictureImagingfield declarations and constructor initialization - Remove all
GdPictureOCRfield declarations and theResourceFolderassignment - Remove all
GdPicturePDFfield declarations used for OCR workflows - Replace each
int imageId = _imaging.CreateGdPictureImage*(...)block withusing var input = new OcrInput(); input.Load*(...) - Replace each
_ocr.SetImage(imageId); _ocr.Language = "..."; string resultId = _ocr.RunOCR();withvar result = new IronTesseract().Read(input); - Replace
_ocr.GetOCRResultText(resultId)withresult.Text - Remove all
_imaging.ReleaseGdPictureImage(imageId)calls - Replace
GdPictureStatuschecks with try/catch blocks - Replace TIFF frame loops with
input.LoadImageFrames(path) - Replace
pdf.OcrPage(...)+pdf.SaveToFile(...)withresult.SaveAsSearchablePdf(outputPath) - Remove the resource folder path from deployment scripts and Docker images
- Update
using GdPicture14;tousing IronOcr;across all affected files
Post-Migration
After completing all code changes, verify the following before deploying to production:
- Single image OCR returns the expected text with no
NullReferenceExceptionfrom removed components - Multi-page TIFF processing covers all frames and produces per-page text through
result.Pages - PDF OCR processes all pages without memory growth over a batch of 50+ documents
- Stream input accepts
MemoryStreamandFileStreamwithout intermediate file writes - Async OCR integrates with ASP.NET Core request handlers without blocking the thread pool
- Parallel batch processing with
Parallel.ForEachproduces accurate results across all threads - All language packs (French, German, etc.) activate correctly through NuGet packages
- Preprocessing filters (deskew, denoise) improve accuracy on scanned documents
- Searchable PDF output is readable by PDF viewers and text-search applications
- No GdPicture namespace references remain in any
.csfile after migration - Memory profile shows stable usage under sustained load (no leaked image allocations)
- Deployment succeeds on Linux and Docker targets without filesystem path errors
Key Benefits of Migrating to IronOCR
Compiler-Enforced Memory Safety. The image ID lifecycle that GdPicture requires developers to maintain manually disappears entirely. OcrInput implements IDisposable, and using blocks enforce cleanup at the end of every scope — including exception paths. No List<int> to maintain, no finally blocks to remember, no production memory incidents from missed release calls.
Single Package for Every OCR Scenario. Image OCR, PDF OCR, multi-page TIFF processing, searchable PDF generation, preprocessing filters, barcode reading, and 125+ language packs are all available from the IronOcr NuGet package and its language companions. There is no feature gate that requires a second or third license purchase before a common workflow becomes possible. See the IronOCR feature overview for the complete capability list.
Native Async and Thread Safety. ReadAsync is a genuine async method, not a Task.Run wrapper. IronTesseract is safe to use across threads without synchronization. Document processing services that previously required per-thread component initialization and semaphore serialization reduce to a single shared instance with Parallel.ForEach. The async OCR guide covers both ASP.NET Core and hosted service patterns.
Zero Deployment Configuration. IronOCR requires no filesystem paths, no external language files, no native binary placements, and no deployment scripts. The NuGet restore step provides everything the application needs. Docker images, Azure App Service deployments, and Linux servers all work identically to the development machine. The Docker deployment guide and Azure deployment guide demonstrate production-ready configurations.
Version-Stable Namespace. using IronOcr has not changed across major version releases. NuGet version numbers handle versioning through the standard package management model. Future major upgrades do not require a codebase-wide find-and-replace of the namespace import.
Predictable Perpetual Licensing. IronOCR is priced at $999 (Lite), $1,499 (Plus), $2,999 (Professional), and $5,999 (Unlimited) — one-time perpetual purchases that include one year of updates, with optional renewal annually. All features are available at every tier. There are no per-feature plugins, no per-page costs, and no maintenance obligation after the first year. Teams that previously absorbed $8,000+ in GdPicture plugin licenses for an OCR-only workflow recover that gap within the first license cycle. Full pricing details are available on the IronOCR licensing page.
Frequently Asked Questions
Why should I migrate from GdPicture.NET OCR to IronOCR?
Common drivers include eliminating COM interop complexity, replacing file-based license management, avoiding per-page billing, enabling Docker/container deployment, and adopting a NuGet-native workflow that integrates with standard .NET tooling.
What are the main code changes when migrating from GdPicture.NET OCR to IronOCR?
Replace GdPicture.NET initialization sequences with IronTesseract instantiation, remove COM lifecycle management (explicit Create/Load/Close patterns), and update result property names. The result is significantly fewer boilerplate lines.
How do I install IronOCR to begin the migration?
Run 'Install-Package IronOcr' in Package Manager Console or 'dotnet add package IronOcr' in the CLI. Language packs are separate packages: 'dotnet add package IronOcr.Languages.French' for French, for example.
Does IronOCR match the OCR accuracy of GdPicture.NET OCR for standard business documents?
IronOCR achieves high accuracy for standard business content including invoices, contracts, receipts, and typed forms. Image preprocessing filters (deskew, noise removal, contrast enhancement) further improve recognition on degraded input.
How does IronOCR handle the language data that GdPicture.NET OCR installs separately?
Language data in IronOCR is distributed as NuGet packages. 'dotnet add package IronOcr.Languages.German' installs German support. No manual file placement or directory paths are involved.
Does migrating from GdPicture.NET OCR to IronOCR require changes to deployment infrastructure?
IronOCR requires fewer infrastructure changes than GdPicture.NET OCR. There are no SDK binary paths, license file placements, or license server configurations. The NuGet package contains the complete OCR engine, and the license key is a string set in application code.
How do I configure IronOCR licensing after migration?
Assign IronOcr.License.LicenseKey = "YOUR-KEY" in application startup code. In Docker or Kubernetes, store the key as an environment variable and read it in startup. Use License.IsValidLicense to validate before accepting traffic.
Can IronOCR process PDFs the same way GdPicture.NET does?
Yes. IronOCR reads both native and scanned PDFs. Instantiate IronTesseract, call ocr.Read(input) where input is a PDF path or OcrPdfInput, and iterate the OcrResult pages. No separate PDF rendering pipeline is required.
How does IronOCR handle threading in high-volume processing?
IronTesseract is safe to instantiate per-thread. Spin up one instance per thread in a Parallel.ForEach or Task pool, run OCR concurrently, and dispose each instance when done. No global state or locking is required.
What output formats does IronOCR support after text extraction?
IronOCR returns structured results including text, word coordinates, confidence scores, and page structure. Export options include plain text, searchable PDF, and structured result objects for downstream processing.
Is IronOCR pricing more predictable than GdPicture.NET OCR for scaling workloads?
IronOCR uses flat-rate perpetual licensing with no per-page or volume charges. Whether you process 10,000 or 10 million pages, the license cost remains constant. Volume and team licensing options are on the IronOCR pricing page.
What happens to my existing tests after migrating from GdPicture.NET OCR to IronOCR?
Tests that assert on extracted text content should continue to pass after migration. Tests that validate API call patterns or COM object lifecycle will need updating to reflect IronOCR's simpler initialization and result model.

