Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ targets = ["x86_64-pc-windows-msvc", "aarch64-pc-windows-msvc", "i686-pc-windows

[dependencies]
image = "0.25"
libloading = "0.8.7"
thiserror = "2.0.12"
serde = { version = "1.0.219", features = ["derive"] }
windows-link = "0.1.3"

[dev-dependencies]
imageproc = "0.25"
Expand Down
7 changes: 2 additions & 5 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,14 @@ pub enum OneOcrError {
#[error("Image format not supported: {0}")]
ImageFormatError(String),

#[error("Failed to load library: {0}")]
LibraryLoadError(#[from] libloading::Error),

#[error("Failed to load model file: {0}")]
ModelFileLoadError(String),

#[error("Invalid model decryption key: {0}")]
InvalidModelKey(String),

#[error("Failed to run ocr API {result}, result: {message}")]
OcrApiError { result: i64, message: String },
#[error("Failed to run OCR API (code: {result}): {message}")]
OcrApiError { result: i32, message: String },

#[error("Other error: {0}")]
Other(String),
Expand Down
71 changes: 34 additions & 37 deletions src/ffi.rs
Original file line number Diff line number Diff line change
@@ -1,43 +1,40 @@
use std::ffi::c_char;
use std::ffi::{c_char, c_void};
use windows_link::link;

pub type CreateOcrInitOptions = unsafe extern "C" fn(*mut i64) -> i64;
pub type OcrInitOptionsSetUseModelDelayLoad = unsafe extern "C" fn(i64, c_char) -> i64;
pub type CreateOcrPipeline = unsafe extern "C" fn(
link!("oneocr.dll" "system" fn CreateOcrInitOptions(init_option: *mut *mut c_void) -> i32);
link!("oneocr.dll" "system" fn OcrInitOptionsSetUseModelDelayLoad(init_option: *mut c_void) -> i32);
link!("oneocr.dll" "system" fn CreateOcrPipeline(
model_path: *const c_char,
key: *const c_char,
ctx: i64,
pipeline: *mut i64,
) -> i64;

pub type CreateOcrProcessOptions = unsafe extern "C" fn(*mut i64) -> i64;
pub type OcrProcessOptionsGetMaxRecognitionLineCount = unsafe extern "C" fn(i64, *mut i64) -> i64;
pub type OcrProcessOptionsSetMaxRecognitionLineCount = unsafe extern "C" fn(i64, i64) -> i64;
pub type OcrProcessOptionsGetResizeResolution =
unsafe extern "C" fn(i64, *mut i64, *mut i64) -> i64;
pub type OcrProcessOptionsSetResizeResolution = unsafe extern "C" fn(i64, i64, i64) -> i64;

/// Image resolution must be great than 50*50, otherwise it will return error code 3.
/// For images with a resolution less than 50*50, you should manually scale up the image first.
pub type RunOcrPipeline = unsafe extern "C" fn(i64, *const RawImage, i64, *mut i64) -> i64;

pub type GetImageAngle = unsafe extern "C" fn(i64, *mut f32) -> i64;

pub type GetOcrLineCount = unsafe extern "C" fn(i64, *mut i64) -> i64;
pub type GetOcrLine = unsafe extern "C" fn(i64, i64, *mut i64) -> i64;
pub type GetOcrLineContent = unsafe extern "C" fn(i64, *mut i64) -> i64;
pub type GetOcrLineBoundingBox = unsafe extern "C" fn(i64, *mut *const RawBBox) -> i64;
pub type GetOcrLineStyle = unsafe extern "C" fn(i64, *mut i32, *mut f32) -> i64;

pub type GetOcrLineWordCount = unsafe extern "C" fn(i64, *mut i64) -> i64;
pub type GetOcrWord = unsafe extern "C" fn(i64, i64, *mut i64) -> i64;
pub type GetOcrWordContent = unsafe extern "C" fn(i64, *mut i64) -> i64;
pub type GetOcrWordBoundingBox = unsafe extern "C" fn(i64, *mut *const RawBBox) -> i64;
pub type GetOcrWordConfidence = unsafe extern "C" fn(i64, *mut f32) -> i64;

pub type ReleaseOcrResult = unsafe extern "C" fn(i64);
pub type ReleaseOcrInitOptions = unsafe extern "C" fn(i64);
pub type ReleaseOcrPipeline = unsafe extern "C" fn(i64);
pub type ReleaseOcrProcessOptions = unsafe extern "C" fn(i64);
ctx: *mut c_void,
pipeline: *mut *mut c_void
) -> i32);
link!("oneocr.dll" "system" fn CreateOcrProcessOptions(option: *mut *mut c_void) -> i32);
link!("oneocr.dll" "system" fn OcrProcessOptionsGetMaxRecognitionLineCount(option: *mut c_void, count: *mut i32) -> i32);
link!("oneocr.dll" "system" fn OcrProcessOptionsSetMaxRecognitionLineCount(option: *mut c_void, count: i32) -> i32);
link!("oneocr.dll" "system" fn OcrProcessOptionsGetResizeResolution(option: *mut c_void, width: *mut i64, height: *mut i64) -> i32);
link!("oneocr.dll" "system" fn OcrProcessOptionsSetResizeResolution (option: *mut c_void, width: i32, height: i32) -> i32);
link!("oneocr.dll" "system" fn RunOcrPipeline(
pipeline: *mut c_void,
image: *const RawImage,
process_options: *mut c_void,
result: *mut *mut c_void
) -> i32);
link!("oneocr.dll" "system" fn GetImageAngle(pipeline: *mut c_void, angle: *mut f32) -> i32);
link!("oneocr.dll" "system" fn GetOcrLineCount(result: *mut c_void, count: *mut i64) -> i32);
link!("oneocr.dll" "system" fn GetOcrLine(result: *mut c_void, index: i64, line: *mut *mut c_void) -> i32);
link!("oneocr.dll" "system" fn GetOcrLineContent(line: *mut c_void, content: *mut *const c_char) -> i32);
link!("oneocr.dll" "system" fn GetOcrLineBoundingBox(line: *mut c_void, bbox: *mut *const RawBBox) -> i32);
link!("oneocr.dll" "system" fn GetOcrLineStyle(line: *mut c_void, style: *mut i32, confidence: *mut f32) -> i32);
link!("oneocr.dll" "system" fn GetOcrLineWordCount(line: *mut c_void, count: *mut i64) -> i32);
link!("oneocr.dll" "system" fn GetOcrWord(line: *mut c_void, index: i64, word: *mut *mut c_void) -> i32);
link!("oneocr.dll" "system" fn GetOcrWordContent(word: *mut c_void, content: *mut *const c_char) -> i32);
link!("oneocr.dll" "system" fn GetOcrWordBoundingBox(word: *mut c_void, bbox: *mut *const RawBBox) -> i32);
link!("oneocr.dll" "system" fn GetOcrWordConfidence(word: *mut c_void, confidence: *mut f32) -> i32);
link!("oneocr.dll" "system" fn ReleaseOcrResult(result: *mut c_void));
link!("oneocr.dll" "system" fn ReleaseOcrInitOptions(init_options: *mut c_void));
link!("oneocr.dll" "system" fn ReleaseOcrPipeline(pipeline: *mut c_void));
link!("oneocr.dll" "system" fn ReleaseOcrProcessOptions(process_options: *mut c_void));

/// This `RawImage` struct represents an image in a format suitable for OCR processing. Used for FFI.
/// - t: Type of the image (e.g., RGB, RGBA).
Expand Down
38 changes: 0 additions & 38 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,44 +18,6 @@ pub use ocr_word::OcrWord;
pub(crate) const ONE_OCR_MODEL_FILE_NAME: &str = "oneocr.onemodel";
pub(crate) const ONE_OCR_MODEL_KEY: &str = r#"kj)TGtrK>f]b[Piow.gU+nC@s""""""4"#;

/// A macro to load a symbol from the library.
/// This macro takes three arguments:
/// - `$library`: The library from which to load the symbol.
/// - `$var_name`: The name of the variable to store the loaded symbol.
/// - `$symbol_name_type`: The type of the symbol to load.
///
/// This macro is used to simplify the process of loading symbols from the library.
/// It helps to avoid repetitive code and makes the code cleaner and more readable.
macro_rules! load_symbol {
($library:expr, $var_name:ident, $symbol_name_type:ident) => {
let $var_name: libloading::Symbol<$symbol_name_type> =
unsafe { $library.get(stringify!($symbol_name_type).as_bytes())? };
};
}

pub(crate) use load_symbol;

/// A macro to attempt to load a symbol and call it, for use in contexts like `drop`
/// Errors during symbol loading are logged to stderr, and the call is skipped.
/// - `$library`: The library instance.
/// - `$symbol_name_type`: The type of the FFI function (also used as the symbol name).
/// - $($arg:expr),*`: The arguments to pass to the function if loaded successfully.
macro_rules! release_ocr_resource {
($library:expr, $symbol_name_type:ident, $($arg:expr),* ) => {
match unsafe { $library.get::<$symbol_name_type>(stringify!($symbol_name_type).as_bytes()) } {
Ok(func_symbol) => {
unsafe { func_symbol($($arg),*) };
}
Err(_) => {
// Ignore the error, as this is best effort
// and we are in the drop context.
}
}
};
}

pub(crate) use release_ocr_resource;

/// A macro to check the result of an OCR call and return an error if it fails.
/// This macro takes an expression `$call` and an error message `$err_msg`.
/// If the result of `$call` is not 0, it returns an `OneOcrError::OcrApiError` error with the provided message.
Expand Down
120 changes: 34 additions & 86 deletions src/ocr_engine.rs
Original file line number Diff line number Diff line change
@@ -1,57 +1,41 @@
use crate::errors::OneOcrError;
use crate::ocr_result::OcrResult;
use crate::{ONE_OCR_MODEL_FILE_NAME, ONE_OCR_MODEL_KEY};
use image::DynamicImage;
use libloading::Library;
use std::ffi::{CString, c_char};
use std::path::Path;

// FFI types
use crate::ffi::{
CreateOcrInitOptions, CreateOcrPipeline, CreateOcrProcessOptions,
OcrInitOptionsSetUseModelDelayLoad, OcrProcessOptionsGetMaxRecognitionLineCount,
OcrProcessOptionsGetResizeResolution, OcrProcessOptionsSetMaxRecognitionLineCount,
OcrProcessOptionsSetResizeResolution, RawImage, ReleaseOcrInitOptions, ReleaseOcrPipeline,
ReleaseOcrProcessOptions, RunOcrPipeline,
};
use crate::ocr_result::OcrResult;
use crate::{ONE_OCR_MODEL_FILE_NAME, ONE_OCR_MODEL_KEY};
use image::DynamicImage;
use std::ffi::{CString, c_void};
use std::path::Path;
use std::ptr;

// Macros
use crate::{check_ocr_call, load_symbol, release_ocr_resource};
use crate::check_ocr_call;

/// The `OcrEngine` struct represents the OneOcr processing engine.
#[derive(Debug)]
pub struct OcrEngine {
lib: Library,
init_options: i64,
pipeline: i64,
process_options: i64,
init_options: *mut c_void,
pipeline: *mut c_void,
process_options: *mut c_void,
}

impl OcrEngine {
/// Creates a new instance of the OCR engine.
/// This function loads the necessary library and initializes the OCR pipeline.
pub fn new() -> Result<Self, OneOcrError> {
let lib = unsafe { Library::new("oneocr.dll")? };

load_symbol!(lib, create_ocr_init_options, CreateOcrInitOptions);
load_symbol!(
lib,
ocr_init_options_set_use_model_delay_load,
OcrInitOptionsSetUseModelDelayLoad
);
load_symbol!(lib, create_ocr_pipeline, CreateOcrPipeline);
load_symbol!(lib, create_ocr_process_options, CreateOcrProcessOptions);

let mut init_options: i64 = 0;
let mut init_options: *mut c_void = ptr::null_mut();
check_ocr_call!(
unsafe { create_ocr_init_options(&mut init_options) },
unsafe { CreateOcrInitOptions(&mut init_options) },
"Failed to create init options"
);

// The FFI function OcrInitOptionsSetUseModelDelayLoad expects a c_char (i8).
// In C, char can be signed or unsigned by default depending on the compiler/platform.
// Rust's c_char is i8. Assuming 0 is a valid value for false.
check_ocr_call!(
unsafe { ocr_init_options_set_use_model_delay_load(init_options, 0 as c_char) },
unsafe { OcrInitOptionsSetUseModelDelayLoad(init_options) },
"Failed to set model delay load"
);

Expand All @@ -67,10 +51,10 @@ impl OcrEngine {
OneOcrError::InvalidModelKey(format!("Failed to convert model key to CString: {}", e))
})?;

let mut pipeline: i64 = 0;
let mut pipeline: *mut c_void = ptr::null_mut();
check_ocr_call!(
unsafe {
create_ocr_pipeline(
CreateOcrPipeline(
model_path_cstr.as_ptr(),
key_cstr.as_ptr(),
init_options,
Expand All @@ -80,14 +64,13 @@ impl OcrEngine {
"Failed to create OCR pipeline"
);

let mut process_options: i64 = 0;
let mut process_options: *mut c_void = ptr::null_mut();
check_ocr_call!(
unsafe { create_ocr_process_options(&mut process_options) },
unsafe { CreateOcrProcessOptions(&mut process_options) },
"Failed to create OCR process options"
);

Ok(Self {
lib,
init_options,
pipeline,
process_options,
Expand All @@ -96,16 +79,11 @@ impl OcrEngine {

/// Retrieves the maximum number of lines that can be recognized.
/// Default is 100.
pub fn get_max_recognition_line_count(&self) -> Result<i64, OneOcrError> {
load_symbol!(
self.lib,
ocr_process_options_get_max_recognition_line_count,
OcrProcessOptionsGetMaxRecognitionLineCount
);
let mut count: i64 = 0;
pub fn get_max_recognition_line_count(&self) -> Result<i32, OneOcrError> {
let mut count: i32 = 0;
check_ocr_call!(
unsafe {
ocr_process_options_get_max_recognition_line_count(self.process_options, &mut count)
OcrProcessOptionsGetMaxRecognitionLineCount(self.process_options, &mut count)
},
"Failed to get max recognition line count"
);
Expand All @@ -114,16 +92,9 @@ impl OcrEngine {

/// Sets the maximum number of lines that can be recognized.
/// Default is 100, range is 0-1000.
pub fn set_max_recognition_line_count(&self, count: i64) -> Result<(), OneOcrError> {
load_symbol!(
self.lib,
ocr_process_options_set_max_recognition_line_count,
OcrProcessOptionsSetMaxRecognitionLineCount
);
pub fn set_max_recognition_line_count(&self, count: i32) -> Result<(), OneOcrError> {
check_ocr_call!(
unsafe {
ocr_process_options_set_max_recognition_line_count(self.process_options, count)
},
unsafe { OcrProcessOptionsSetMaxRecognitionLineCount(self.process_options, count) },
"Failed to set max recognition line count"
);
Ok(())
Expand All @@ -136,20 +107,11 @@ impl OcrEngine {
///
/// Default is 1152*768.
pub fn get_resize_resolution(&self) -> Result<(i64, i64), OneOcrError> {
load_symbol!(
self.lib,
ocr_process_options_get_resize_resolution,
OcrProcessOptionsGetResizeResolution
);
let mut width: i64 = 0;
let mut height: i64 = 0;
check_ocr_call!(
unsafe {
ocr_process_options_get_resize_resolution(
self.process_options,
&mut width,
&mut height,
)
OcrProcessOptionsGetResizeResolution(self.process_options, &mut width, &mut height)
},
"Failed to get resize resolution"
);
Expand All @@ -162,16 +124,9 @@ impl OcrEngine {
/// It’s a performance and accuracy trade-off rather than a restriction on the original image’s resolution.
///
/// The maximum resolution is 1152*768.
pub fn set_resize_resolution(&self, width: i64, height: i64) -> Result<(), OneOcrError> {
load_symbol!(
self.lib,
ocr_process_options_set_resize_resolution,
OcrProcessOptionsSetResizeResolution
);
pub fn set_resize_resolution(&self, width: i32, height: i32) -> Result<(), OneOcrError> {
check_ocr_call!(
unsafe {
ocr_process_options_set_resize_resolution(self.process_options, width, height)
},
unsafe { OcrProcessOptionsSetResizeResolution(self.process_options, width, height) },
"Failed to set resize resolution"
);
Ok(())
Expand Down Expand Up @@ -206,22 +161,13 @@ impl OcrEngine {
data_ptr,
};

load_symbol!(self.lib, run_ocr_pipeline, RunOcrPipeline);

let mut ocr_result_handle: i64 = 0;
let mut ocr_result: *mut c_void = ptr::null_mut();
check_ocr_call!(
unsafe {
run_ocr_pipeline(
self.pipeline,
&image,
self.process_options,
&mut ocr_result_handle,
)
},
unsafe { RunOcrPipeline(self.pipeline, &image, self.process_options, &mut ocr_result) },
"Failed to run OCR pipeline"
);

OcrResult::new(&self.lib, ocr_result_handle, word_level_detail)
OcrResult::new(ocr_result, word_level_detail)
}

/// Retrieves the path to the model file.
Expand All @@ -245,8 +191,10 @@ impl OcrEngine {

impl Drop for OcrEngine {
fn drop(&mut self) {
release_ocr_resource!(self.lib, ReleaseOcrPipeline, self.pipeline);
release_ocr_resource!(self.lib, ReleaseOcrInitOptions, self.init_options);
release_ocr_resource!(self.lib, ReleaseOcrProcessOptions, self.process_options);
unsafe {
ReleaseOcrPipeline(self.pipeline);
ReleaseOcrInitOptions(self.init_options);
ReleaseOcrProcessOptions(self.process_options);
};
}
}
Loading