Release v0.6.8: add background image OCR and optional Tesseract
This commit is contained in:
+115
@@ -271,4 +271,119 @@ std::wstring recognizeText(const Clip &clip)
|
||||
WindowsDeleteString(text);
|
||||
return out;
|
||||
}
|
||||
std::filesystem::path tesseractExecutable(const Settings &s)
|
||||
{
|
||||
if (!s.tesseractPath.empty())
|
||||
{
|
||||
std::filesystem::path path(s.tesseractPath);
|
||||
if (!path.is_absolute()) throw std::runtime_error("Select an absolute path to tesseract.exe");
|
||||
return path;
|
||||
}
|
||||
for (auto variable : {L"ProgramW6432", L"ProgramFiles", L"LOCALAPPDATA"})
|
||||
{
|
||||
wchar_t base[32768]{};
|
||||
auto length = GetEnvironmentVariableW(variable, base, std::size(base));
|
||||
if (!length || length >= std::size(base)) continue;
|
||||
auto root = std::filesystem::path(base);
|
||||
for (auto relative : {L"Tesseract-OCR/tesseract.exe", L"Programs/Tesseract-OCR/tesseract.exe"})
|
||||
if (std::filesystem::is_regular_file(root / relative)) return root / relative;
|
||||
}
|
||||
throw std::runtime_error("Tesseract was not found. Install the optional 64-bit Tesseract 5 package or select tesseract.exe in OCR options.");
|
||||
}
|
||||
std::wstring ocrProfile(const Settings &s)
|
||||
{
|
||||
return s.useTesseract ? L"tesseract:" + lower(s.tesseractPath) + L":" + s.tesseractLanguages : L"windows";
|
||||
}
|
||||
namespace
|
||||
{
|
||||
struct TessLibrary
|
||||
{
|
||||
HMODULE module{};
|
||||
explicit TessLibrary(const Settings &s)
|
||||
{
|
||||
auto exe = tesseractExecutable(s);
|
||||
if (!std::filesystem::is_regular_file(exe)) throw std::runtime_error("Tesseract executable does not exist");
|
||||
auto library = exe.parent_path() / L"libtesseract-5.dll";
|
||||
module = LoadLibraryExW(library.c_str(), nullptr,
|
||||
LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
|
||||
if (!module) throw std::runtime_error("Cannot load Tesseract 5. Select a complete 64-bit installation with libtesseract-5.dll and its dependencies.");
|
||||
}
|
||||
~TessLibrary() { if (module) FreeLibrary(module); }
|
||||
template<class T> T symbol(const char *name)
|
||||
{
|
||||
auto address = GetProcAddress(module, name);
|
||||
if (!address) throw std::runtime_error(std::string("Unsupported Tesseract library: missing ") + name);
|
||||
T result; static_assert(sizeof(result) == sizeof(address));
|
||||
memcpy(&result, &address, sizeof(result));
|
||||
return result;
|
||||
}
|
||||
};
|
||||
struct TessEngine
|
||||
{
|
||||
TessLibrary library;
|
||||
void *handle{};
|
||||
void (*destroy)(void *){};
|
||||
explicit TessEngine(const Settings &s) : library(s)
|
||||
{
|
||||
auto folder = tesseractExecutable(s).parent_path() / L"tessdata";
|
||||
size_t start = 0;
|
||||
do
|
||||
{
|
||||
auto end = s.tesseractLanguages.find(L'+', start);
|
||||
auto language = s.tesseractLanguages.substr(start, end == std::wstring::npos ? end : end - start);
|
||||
if (!std::filesystem::is_regular_file(folder / (language + L".traineddata")))
|
||||
throw std::runtime_error("Tesseract language is not installed: " + utf8(language));
|
||||
if (end == std::wstring::npos) break;
|
||||
start = end + 1;
|
||||
} while (true);
|
||||
auto create = library.symbol<void *(*)()>("TessBaseAPICreate");
|
||||
destroy = library.symbol<void (*)(void *)>("TessBaseAPIDelete");
|
||||
auto init = library.symbol<int (*)(void *, const char *, const char *)>("TessBaseAPIInit3");
|
||||
handle = create();
|
||||
if (!handle) throw std::runtime_error("Cannot create Tesseract engine");
|
||||
if (init(handle, utf8(folder.wstring()).c_str(), utf8(s.tesseractLanguages).c_str()) != 0)
|
||||
{
|
||||
destroy(handle); handle = nullptr;
|
||||
throw std::runtime_error("Cannot initialize the selected Tesseract languages");
|
||||
}
|
||||
}
|
||||
~TessEngine() { if (handle) destroy(handle); }
|
||||
};
|
||||
}
|
||||
void validateTesseract(const Settings &s)
|
||||
{
|
||||
if (s.useTesseract) { TessEngine engine(s); }
|
||||
}
|
||||
std::wstring recognizeText(const Clip &clip, const Settings &s)
|
||||
{
|
||||
if (!s.useTesseract) return recognizeText(clip);
|
||||
auto apartmentResult = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
if (FAILED(apartmentResult) && apartmentResult != RPC_E_CHANGED_MODE) check(apartmentResult);
|
||||
struct Apartment { bool owned; ~Apartment() { if (owned) CoUninitialize(); } } apartment{SUCCEEDED(apartmentResult)};
|
||||
// Load the optional C API dynamically: Windows OCR users need no Tesseract DLLs.
|
||||
TessEngine engine(s);
|
||||
auto &lib = engine.library;
|
||||
auto image = lib.symbol<void (*)(void *, const unsigned char *, int, int, int, int)>("TessBaseAPISetImage");
|
||||
auto mode = lib.symbol<void (*)(void *, int)>("TessBaseAPISetPageSegMode");
|
||||
auto recognize = lib.symbol<int (*)(void *, void *)>("TessBaseAPIRecognize");
|
||||
auto getText = lib.symbol<char *(*)(void *)>("TessBaseAPIGetUTF8Text");
|
||||
auto freeText = lib.symbol<void (*)(const char *)>("TessDeleteText");
|
||||
auto createMonitor = lib.symbol<void *(*)()>("TessMonitorCreate");
|
||||
auto freeMonitor = lib.symbol<void (*)(void *)>("TessMonitorDelete");
|
||||
auto deadline = lib.symbol<void (*)(void *, int)>("TessMonitorSetDeadlineMSecs");
|
||||
std::unique_ptr<void, void (*)(void *)> monitor(createMonitor(), freeMonitor);
|
||||
if (!monitor) throw std::runtime_error("Cannot create Tesseract progress monitor");
|
||||
auto pixels = decodeImage(clip);
|
||||
Bytes gray(size_t(pixels.width) * pixels.height);
|
||||
for (size_t i = 0; i < gray.size(); ++i)
|
||||
gray[i] = BYTE((29 * pixels.bgra[4*i] + 150 * pixels.bgra[4*i+1] + 77 * pixels.bgra[4*i+2]) >> 8);
|
||||
image(engine.handle, gray.data(), pixels.width, pixels.height, 1, pixels.width);
|
||||
mode(engine.handle, 11); // Sparse text: screenshots often contain separate labels and paragraphs.
|
||||
deadline(monitor.get(), 30000);
|
||||
if (recognize(engine.handle, monitor.get()) != 0)
|
||||
throw std::runtime_error("Tesseract recognition failed or exceeded 30 seconds");
|
||||
std::unique_ptr<char, void (*)(const char *)> text(getText(engine.handle), freeText);
|
||||
if (!text) throw std::runtime_error("Tesseract returned no result");
|
||||
return wide(text.get());
|
||||
}
|
||||
} // namespace ks
|
||||
|
||||
Reference in New Issue
Block a user