96 lines
4.6 KiB
C++
96 lines
4.6 KiB
C++
#include "ocr.h"
|
|
#include "app.h"
|
|
#include <iostream>
|
|
namespace ks {
|
|
struct InputRegressionAccess {
|
|
static void scheduleOcr(App &app) { app.scheduleHistoryOcr(app.settings); }
|
|
};
|
|
}
|
|
int main(int argc, char **argv)
|
|
{
|
|
const bool tesseract = argc == 2 && std::string(argv[1]) == "--tesseract";
|
|
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
|
try
|
|
{
|
|
BITMAPINFO info{};
|
|
info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
|
info.bmiHeader.biWidth = 800;
|
|
info.bmiHeader.biHeight = -150;
|
|
info.bmiHeader.biPlanes = 1;
|
|
info.bmiHeader.biBitCount = 32;
|
|
void *data{};
|
|
HBITMAP bitmap = CreateDIBSection(nullptr, &info, DIB_RGB_COLORS, &data, nullptr, 0);
|
|
if (!bitmap)
|
|
throw std::runtime_error("Cannot create OCR fixture");
|
|
HDC dc = CreateCompatibleDC(nullptr);
|
|
auto old = SelectObject(dc, bitmap);
|
|
RECT rect{0, 0, 800, 150};
|
|
FillRect(dc, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH));
|
|
HFONT font = CreateFontW(-54, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, 0, 0,
|
|
ANTIALIASED_QUALITY, 0, L"Arial");
|
|
auto oldFont = SelectObject(dc, font);
|
|
SetTextColor(dc, RGB(0, 0, 0));
|
|
SetBkColor(dc, RGB(255, 255, 255));
|
|
std::wstring fixture = tesseract ? L"Привіт Україно HELLO" : L"HELLO KSWITCHER";
|
|
TextOutW(dc, 20, 40, fixture.c_str(), (int)fixture.size());
|
|
GdiFlush();
|
|
ks::Pixels pixels{800, 150, ks::Bytes((BYTE *)data, (BYTE *)data + 800 * 150 * 4)};
|
|
SelectObject(dc, oldFont);
|
|
SelectObject(dc, old);
|
|
DeleteObject(font);
|
|
DeleteObject(bitmap);
|
|
DeleteDC(dc);
|
|
ks::Clip clip{{ks::imageDib(pixels)}};
|
|
auto decoded = ks::decodeImage(clip);
|
|
if (decoded.width != 800 || decoded.height != 150)
|
|
throw std::runtime_error("WIC decode size mismatch");
|
|
ks::Settings config;
|
|
config.useTesseract = tesseract;
|
|
auto text = ks::recognizeText(clip, config);
|
|
std::cout << "OCR: " << ks::utf8(text) << '\n';
|
|
if (ks::upper(text).find(L"HELLO") == std::wstring::npos)
|
|
throw std::runtime_error("OCR fixture was not recognized");
|
|
if (tesseract && ks::lower(text).find(L"привіт") == std::wstring::npos)
|
|
throw std::runtime_error("Ukrainian OCR fixture was not recognized");
|
|
auto temp = std::filesystem::temp_directory_path() / (L"KSwitcher-auto-ocr-" + std::to_wstring(GetCurrentProcessId()));
|
|
auto sequence = GetClipboardSequenceNumber();
|
|
{
|
|
ks::App app(GetModuleHandleW(nullptr), true, temp);
|
|
app.settings.history = true;
|
|
app.history->add(clip, L"OCR fixture", app.settings);
|
|
auto id = app.history->entries().front().id;
|
|
if (tesseract)
|
|
{
|
|
app.history->saveOcr(id, L"old Windows result", app.settings);
|
|
app.settings.useTesseract = true;
|
|
}
|
|
ks::InputRegressionAccess::scheduleOcr(app);
|
|
auto deadline = GetTickCount64() + 35000;
|
|
while ((app.history->entries().front().ocrState == 1 ||
|
|
app.history->entries().front().ocrProfile != ks::ocrProfile(app.settings)) &&
|
|
GetTickCount64() < deadline) Sleep(20);
|
|
if (app.history->search(L"HELLO").size() != 1)
|
|
throw std::runtime_error("Background OCR did not make the image searchable");
|
|
ks::History restored(temp / L"history");
|
|
if (!restored.ocrText(id) || restored.search(L"HELLO").size() != 1)
|
|
throw std::runtime_error("Background OCR did not persist its result");
|
|
if (tesseract && (restored.search(L"привіт").size() != 1 || !restored.search(L"old Windows result").empty()))
|
|
throw std::runtime_error("Engine change did not replace cached Windows OCR with Ukrainian text");
|
|
if (GetClipboardSequenceNumber() != sequence)
|
|
throw std::runtime_error("Background OCR changed the clipboard");
|
|
std::cout << "PASS automatic background OCR, persisted search and unchanged clipboard\n";
|
|
}
|
|
std::filesystem::remove_all(temp);
|
|
std::cout << (tesseract ? "PASS optional Tesseract, mixed Ukrainian/English and cache migration\n" :
|
|
"PASS native Windows OCR, DIB decoding and async result retrieval\n");
|
|
CoUninitialize();
|
|
return 0;
|
|
}
|
|
catch (const std::exception &e)
|
|
{
|
|
std::cerr << e.what() << '\n';
|
|
CoUninitialize();
|
|
return 1;
|
|
}
|
|
}
|