From 8f1add67dfb8a246f85a321d024a436dccdbb88e Mon Sep 17 00:00:00 2001 From: Takashi Sawanaka Date: Sat, 16 Jan 2021 21:23:45 +0900 Subject: [PATCH] Extract text from image using Windows.Media.Ocr.OcrEngine (#12) * WIP: Extract text from image using Windows.Media.Ocr.OcrEngine * Fix crash when pasting non-32bit bitmap from clipboard * WIP: Extract text from image using Windows.Media.Ocr.OcrEngine (2) --- src/ImgMergeWindow.hpp | 110 ++++++- src/Ocr.hpp | 375 ++++++++++++++++++++++++ src/Win78Libraries.cpp | 18 ++ src/Win78Libraries.h | 4 + src/WinIMerge.cpp | 23 +- src/WinIMerge.rc | 5 +- src/WinIMergeLib.h | 5 + src/WinIMergeLib.vs2017.vcxproj | 1 + src/WinIMergeLib.vs2017.vcxproj.filters | 3 + src/WinIMergeLib.vs2019.vcxproj | 1 + src/WinIMergeLib.vs2019.vcxproj.filters | 3 + src/resource.h | 4 +- 12 files changed, 547 insertions(+), 5 deletions(-) create mode 100644 src/Ocr.hpp diff --git a/src/ImgMergeWindow.hpp b/src/ImgMergeWindow.hpp index 2f51fcb..267dfce 100644 --- a/src/ImgMergeWindow.hpp +++ b/src/ImgMergeWindow.hpp @@ -23,6 +23,7 @@ #include "FreeImagePlus.h" #include "ImgWindow.hpp" #include "ImgMergeBuffer.hpp" +#include "Ocr.hpp" #include "WinIMergeLib.h" @@ -693,6 +694,7 @@ class CImgMergeWindow : public IImgMergeWindow CImgWindow& imgWindow = m_imgWindow[pane]; fipImageEx image; image.pasteFromClipboard(); + image.convertTo32Bits(); int maxwidth = (std::max)(m_buffer.GetImageWidth(pane), static_cast(image.getWidth())); int maxheight = (std::max)(m_buffer.GetImageHeight(pane), static_cast(image.getHeight())); m_buffer.Resize(pane, maxwidth, maxheight); @@ -982,7 +984,22 @@ class CImgMergeWindow : public IImgMergeWindow std::map metadata = m_buffer.GetOriginalImage(pane)->getMetadata(); std::string metadatastr; for (auto& it : metadata) - metadatastr += it.first + ": " + it.second + "\r\n"; + { + std::string value = it.second; + wchar_t c = value.front(); + if ((c == '\'' || c == '"' || c == '>' || c == '|' || c == ':' || c == '{' || c == '\r' || c == '\n') + || value.find_first_of("\r\n") != std::string::npos || value.find(": ") != std::string::npos) + { + for (int i = static_cast(value.length() - 1); i >= 0; --i) + { + if (value[i] == '\n' || (i < static_cast(value.length()) - 1 && value[i] == '\r' && value[i + 1] != '\n')) + value.insert(i + 1, " "); + } + metadatastr += it.first + ": |\n " + value + "\n"; + } + else + metadatastr += it.first + ": " + value + "\n"; + } if (buf) { if (bufsize >= metadatastr.length() + 1) @@ -998,7 +1015,96 @@ class CImgMergeWindow : public IImgMergeWindow return metadatastr.length() + 1; } + BSTR ExtractTextFromImage(int pane, int page, OCR_RESULT_TYPE resultType) override + { + if (!m_pOcr) + m_pOcr.reset(new ocr::COcr()); + if (!m_pOcr || + pane < 0 || pane >= m_buffer.GetPaneCount() || + page >= m_buffer.GetPageCount(pane)) + return nullptr; + + std::wstring text; + int oldCurrentPage = m_buffer.GetCurrentPage(pane); + int minpage, maxpage; + if (page == -1) + { + minpage = 0; + maxpage = m_buffer.GetPageCount(pane) - 1; + } + else + { + minpage = page; + maxpage = page; + } + for (int p = minpage; p <= maxpage; ++p) + { + wchar_t filename[MAX_PATH] = {}; + _snwprintf_s(filename, _TRUNCATE, L"%s/WinIMerge_ocr_%d_%d_%d.png", + _wgetenv(L"TEMP"), GetCurrentProcessId(), pane, p); + m_buffer.SetCurrentPage(pane, p); + const_cast(m_buffer.GetOriginalImage32(pane))->save(filename); + + m_pOcr->load(filename); + + DeleteFile(filename); + + ocr::Result result; + m_pOcr->extractText(result); + wchar_t buf[256]; + + if (resultType != OCR_RESULT_TYPE::TEXT_ONLY) + { + _snwprintf_s(buf, _TRUNCATE, L"- page: %d\n content:\n", p + 1); + text.append(buf); + } + for (auto&& line : result.lines) + { + switch (resultType) + { + case OCR_RESULT_TYPE::TEXT_ONLY: + text.append(line.text); + text.append(L"\n"); + break; + case OCR_RESULT_TYPE::TEXT_PER_LINE_YAML: + { + ocr::Word& lastWord = line.words[line.words.size() - 1]; + auto minXi = std::min_element(line.words.begin(), line.words.end(), + [](const ocr::Word& a, const ocr::Word& b) { return a.rect.x < b.rect.x; }); + auto minYi = std::min_element(line.words.begin(), line.words.end(), + [](const ocr::Word& a, const ocr::Word& b) { return a.rect.y < b.rect.y; }); + auto maxYi = std::max_element(line.words.begin(), line.words.end(), + [](const ocr::Word& a, const ocr::Word& b) { return a.rect.y + a.rect.height < b.rect.y + b.rect.height; }); + _snwprintf_s(buf, _TRUNCATE, L" - rect: {x: %.0f, y: %.0f, w: %.0f, h: %.0f}\n text: |\n ", + minXi->rect.x, minYi->rect.y, + lastWord.rect.x + lastWord.rect.width - line.words[0].rect.x, + maxYi->rect.y + maxYi->rect.height - minYi->rect.y); + text.append(buf); + text.append(line.text); + text.append(L"\n"); + break; + } + case OCR_RESULT_TYPE::TEXT_PER_WORD_YAML: + text.append(L" -\n"); + for (auto&& word : line.words) + { + _snwprintf_s(buf, _TRUNCATE, L" - rect: {x: %.0f, y: %.0f, w: %.0f, h: %.0f}\n text: |\n ", + word.rect.x, word.rect.y, + word.rect.width, word.rect.height); + text.append(buf); + text.append(word.text); + text.append(L"\n"); + } + break; + } + } + } + + m_buffer.SetCurrentPage(pane, oldCurrentPage); + + return SysAllocStringLen(text.c_str(), static_cast(text.size())); + } private: @@ -1766,5 +1872,5 @@ class CImgMergeWindow : public IImgMergeWindow DRAGGING_MODE m_draggingModeCurrent; CImgMergeBuffer m_buffer; ULONG_PTR m_gdiplusToken; - + std::unique_ptr m_pOcr; }; diff --git a/src/Ocr.hpp b/src/Ocr.hpp new file mode 100644 index 0000000..995164c --- /dev/null +++ b/src/Ocr.hpp @@ -0,0 +1,375 @@ +#pragma once + +#include +#include + +namespace ocr +{ +struct Rect +{ + float x; + float y; + float width; + float height; +}; + +struct Word +{ + std::wstring text; + Rect rect; +}; + +struct Line +{ + std::wstring text; + std::vector words; +}; + +struct Result +{ + std::wstring text; + std::optional textAngle; + std::vector lines; +}; + +} + +#ifdef _WIN64 +#include +#include +#include +#include +#include +#include +#include "Win78Libraries.h" + +namespace ocr +{ + +using ABI::Windows::Foundation::IAsyncOperation; +using ABI::Windows::Foundation::IReference; +using ABI::Windows::Foundation::Collections::IVectorView; +using ABI::Windows::Graphics::Imaging::IBitmapDecoder; +using ABI::Windows::Graphics::Imaging::IBitmapDecoderStatics; +using ABI::Windows::Graphics::Imaging::ISoftwareBitmap; +using ABI::Windows::Graphics::Imaging::ISoftwareBitmapStatics; +using ABI::Windows::Graphics::Imaging::IBitmapFrameWithSoftwareBitmap; +using ABI::Windows::Graphics::Imaging::BitmapDecoder; +using ABI::Windows::Graphics::Imaging::SoftwareBitmap; +using ABI::Windows::Media::Ocr::IOcrEngine; +using ABI::Windows::Media::Ocr::IOcrEngineStatics; +using ABI::Windows::Media::Ocr::IOcrLine; +using ABI::Windows::Media::Ocr::IOcrWord; +using ABI::Windows::Media::Ocr::IOcrResult; +using ABI::Windows::Media::Ocr::OcrLine; +using ABI::Windows::Media::Ocr::OcrWord; +using ABI::Windows::Media::Ocr::OcrResult; +using ABI::Windows::Storage::Streams::IRandomAccessStream; +using ABI::Windows::Storage::FileAccessMode_Read; +using Microsoft::WRL::ComPtr; +using Microsoft::WRL::Wrappers::Event; +using Microsoft::WRL::Wrappers::HStringReference; +using Microsoft::WRL::Wrappers::HString; +using Thread = Microsoft::WRL::Wrappers::HandleT; + +class COcr +{ +public: + ~COcr() + { + if (m_thread.Get()) + { + PostThreadMessage(m_dwThreadId, WM_QUIT, 0, 0); + } + } + + bool isValid() const + { + return m_thread.IsValid(); + } + + bool load(const wchar_t* filename) + { + Microsoft::WRL::Wrappers::Event loadCompleted(CreateEventEx(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, WRITE_OWNER | EVENT_ALL_ACCESS)); + + OcrThreadParams params{}; + params.filename = filename; + params.hEvent = loadCompleted.Get(); + params.type = 0; + params.result = false; + + if (!m_thread.IsValid()) + { + m_thread.Attach(CreateThread(nullptr, 0, OcrWorkerThread, ¶ms, 0, &m_dwThreadId)); + if (!m_thread.IsValid()) + return false; + } + else + { + PostThreadMessage(m_dwThreadId, WM_USER, 0, reinterpret_cast(¶ms)); + } + + WaitForSingleObject(params.hEvent, INFINITE); + + return params.result; + } + + bool extractText(Result& result) + { + if (!isValid()) + return false; + + Event recognizeCompleted(CreateEventEx(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, WRITE_OWNER | EVENT_ALL_ACCESS)); + + OcrThreadParams params{}; + params.hEvent = recognizeCompleted.Get(); + params.type = 1; + + PostThreadMessage(m_dwThreadId, WM_USER, 0, reinterpret_cast(¶ms)); + + WaitForSingleObject(params.hEvent, INFINITE); + + result = std::move(params.ocrResult); + return params.result; + } + +private: + + struct OcrThreadParams + { + const wchar_t* filename; + int type; + HANDLE hEvent; + bool result; + Result ocrResult; + }; + + static bool LoadImage(const wchar_t* filename, ISoftwareBitmap** ppBitmap) + { + ComPtr s; + HRESULT hr = CreateRandomAccessStreamOnFile(filename, static_cast(FileAccessMode_Read), + IID_PPV_ARGS(&s)); + if (FAILED(hr)) + return false; + + ComPtr pBitmapDecoderStatics; + hr = Win78Libraries::RoGetActivationFactory( + HStringReference(RuntimeClass_Windows_Graphics_Imaging_BitmapDecoder).Get(), + IID_PPV_ARGS(&pBitmapDecoderStatics)); + if (FAILED(hr)) + return false; + + ComPtr> pAsync; + hr = pBitmapDecoderStatics->CreateAsync(s.Get(), &pAsync); + if (FAILED(hr)) + return false; + + ComPtr pBitmapDecoder; + hr = Win78Libraries::await(pAsync.Get(), pBitmapDecoder.GetAddressOf()); + if (FAILED(hr)) + return false; + + ComPtr pBitmapFrameWithSoftwareBitmap; + hr = pBitmapDecoder->QueryInterface(&pBitmapFrameWithSoftwareBitmap); + if (FAILED(hr)) + return false; + + ComPtr> pAsync2; + hr = pBitmapFrameWithSoftwareBitmap->GetSoftwareBitmapAsync(&pAsync2); + if (FAILED(hr)) + return false; + + return SUCCEEDED(Win78Libraries::await(pAsync2.Get(), ppBitmap)); + } + + static IOcrEngine* CreateOcrEngine() + { + ComPtr pOcrEngineStatics; + HRESULT hr = Win78Libraries::RoGetActivationFactory( + HStringReference(RuntimeClass_Windows_Media_Ocr_OcrEngine).Get(), + IID_PPV_ARGS(&pOcrEngineStatics)); + if (FAILED(hr)) + return nullptr; + + IOcrEngine* pOcrEngine = nullptr; + hr = pOcrEngineStatics->TryCreateFromUserProfileLanguages(&pOcrEngine); + if (FAILED(hr)) + return nullptr; + + return pOcrEngine; + } + + static std::wstring HStringToWstring(const HString& htext) + { + unsigned int length = 0; + const wchar_t* ptr = htext.GetRawBuffer(&length); + if (!ptr) + return L""; + return std::wstring(ptr, length); + } + + static bool Recognize(IOcrEngine* pOcrEngine, ISoftwareBitmap* pBitmap, Result& result) + { + result.textAngle.reset(); + result.lines.clear(); + + if (!pOcrEngine || !pBitmap) + return false; + + ComPtr> pAsync; + HRESULT hr = pOcrEngine->RecognizeAsync(pBitmap, &pAsync); + if (FAILED(hr)) + return false; + + ComPtr pOcrResult; + hr = Win78Libraries::await(pAsync.Get(), pOcrResult.GetAddressOf()); + if (FAILED(hr)) + return false; + + HString htext; + ComPtr> pTextAngle; + pOcrResult->get_TextAngle(&pTextAngle); + if (pTextAngle) + { + double angle; + pTextAngle->get_Value(&angle); + result.textAngle = angle; + } + + ComPtr> pOcrLines; + hr = pOcrResult->get_Lines(&pOcrLines); + if (FAILED(hr)) + return false; + + uint32_t nlines; + hr = pOcrLines->get_Size(&nlines); + if (FAILED(hr)) + return false; + + for (uint32_t i = 0; i < nlines; ++i) + { + Line line; + ComPtr pOcrLine; + hr = pOcrLines->GetAt(i, &pOcrLine); + if (FAILED(hr)) + break; + + ComPtr> pOcrWords; + hr = pOcrLine->get_Words(&pOcrWords); + if (FAILED(hr)) + return false; + + uint32_t nwords; + hr = pOcrWords->get_Size(&nwords); + if (FAILED(hr)) + return false; + + for (uint32_t j = 0; j < nwords; ++j) + { + ComPtr pOcrWord; + hr = pOcrWords->GetAt(j, &pOcrWord); + if (FAILED(hr)) + break; + + ABI::Windows::Foundation::Rect rect; + hr = pOcrWord->get_BoundingRect(&rect); + + hr = pOcrWord->get_Text(htext.GetAddressOf()); + if (FAILED(hr)) + return false; + + std::wstring text = HStringToWstring(htext); + Word word{ text, Rect{rect.X, rect.Y, rect.Width, rect.Height}}; + + if (line.words.size() > 0) + { + const Word& prevword = line.words[line.words.size() - 1]; + if (isascii(prevword.text.back()) || + word.rect.x - (prevword.rect.x + prevword.rect.width) > word.rect.height / 2) + { + line.text.append(L" "); + } + } + + line.words.emplace_back(word); + + line.text.append(text); + } + + result.lines.emplace_back(line); + + result.text.append(line.text); + result.text.append(L"\n"); + } + + return true; + } + + static DWORD WINAPI OcrWorkerThread(LPVOID lpParam) + { + if (Win78Libraries::RoGetActivationFactory == nullptr) + Win78Libraries::load(); + + HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED); + { + ComPtr pBitmap; + ComPtr pOcrEngine(CreateOcrEngine()); + OcrThreadParams* pParam = reinterpret_cast(lpParam); + for (;;) + { + if (pParam->type == 0) + { + pBitmap.Reset(); + pParam->result = LoadImage(pParam->filename, &pBitmap); + } + else + { + pParam->result = Recognize(pOcrEngine.Get(), pBitmap.Get(), pParam->ocrResult); + } + SetEvent(pParam->hEvent); + + MSG msg; + BOOL bRet = GetMessage(&msg, nullptr, 0, 0); + if (bRet == 0 || bRet == -1) + break; + pParam = reinterpret_cast(msg.lParam); + } + } + CoUninitialize(); + return true; + } + +private: + Thread m_thread; + DWORD m_dwThreadId = 0; +}; + +} + +#else + +namespace ocr +{ + +class COcr +{ +public: + bool isValid() const { return false; } + bool load(const wchar_t* filename) { return false; } + bool extractText(Result& result) + { + const std::wstring msg = L"This function is not supported on 32bit version"; + result.text = msg; + Line line; + line.text = msg; + Word word; + word.text = msg; + line.words.emplace_back(word); + result.lines.emplace_back(line); + return false; + } +}; + +} + +#endif diff --git a/src/Win78Libraries.cpp b/src/Win78Libraries.cpp index 23fe57d..9429e1f 100644 --- a/src/Win78Libraries.cpp +++ b/src/Win78Libraries.cpp @@ -6,6 +6,8 @@ namespace Win78Libraries { CreateRandomAccessStreamOnFileType CreateRandomAccessStreamOnFile; WindowsCreateStringReferenceType WindowsCreateStringReference; + WindowsDeleteStringType WindowsDeleteString; + WindowsGetStringRawBufferType WindowsGetStringRawBuffer; RoGetActivationFactoryType RoGetActivationFactory; RoActivateInstanceType RoActivateInstance; D2D1CreateFactoryType D2D1CreateFactory; @@ -25,6 +27,8 @@ namespace Win78Libraries if (hLibraryCombase != nullptr) { WindowsCreateStringReference = reinterpret_cast(GetProcAddress(hLibraryCombase, "WindowsCreateStringReference")); + WindowsDeleteString = reinterpret_cast(GetProcAddress(hLibraryCombase, "WindowsDeleteString")); + WindowsGetStringRawBuffer = reinterpret_cast(GetProcAddress(hLibraryCombase, "WindowsGetStringRawBuffer")); RoGetActivationFactory = reinterpret_cast(GetProcAddress(hLibraryCombase, "RoGetActivationFactory")); RoActivateInstance = reinterpret_cast(GetProcAddress(hLibraryCombase, "RoActivateInstance")); } @@ -84,6 +88,20 @@ STDAPI WindowsCreateStringReference(_In_reads_opt_(length + 1) PCWSTR sourceStri return Win78Libraries::WindowsCreateStringReference(sourceString, length, hstringHeader, string); } +STDAPI WindowsDeleteString(_In_opt_ HSTRING string) +{ + if (Win78Libraries::WindowsDeleteString == nullptr) + return E_FAIL; + return Win78Libraries::WindowsDeleteString(string); +} + +STDAPI_(PCWSTR) WindowsGetStringRawBuffer(_In_opt_ HSTRING string, _Out_opt_ UINT32* length) +{ + if (Win78Libraries::WindowsGetStringRawBuffer == nullptr) + return nullptr; + return Win78Libraries::WindowsGetStringRawBuffer(string, length); +} + HRESULT WINAPI D2D1CreateFactory(_In_ D2D1_FACTORY_TYPE factoryType, _In_ REFIID riid, _In_opt_ CONST D2D1_FACTORY_OPTIONS *pFactoryOptions, _Out_ void **ppIFactory) { if (Win78Libraries::D2D1CreateFactory == nullptr) diff --git a/src/Win78Libraries.h b/src/Win78Libraries.h index 5e5ab2e..d2c35c5 100644 --- a/src/Win78Libraries.h +++ b/src/Win78Libraries.h @@ -11,11 +11,15 @@ namespace Win78Libraries { using CreateRandomAccessStreamOnFileType = HRESULT(__stdcall*)(_In_ PCWSTR filePath, _In_ DWORD accessMode, _In_ REFIID riid, _COM_Outptr_ void** ppv); using WindowsCreateStringReferenceType = HRESULT(__stdcall*)(_In_reads_opt_(length + 1) PCWSTR sourceString, UINT32 length, _Out_ HSTRING_HEADER * hstringHeader, _Outptr_result_maybenull_ _Result_nullonfailure_ HSTRING * string); + using WindowsDeleteStringType = HRESULT(__stdcall*)(_In_opt_ HSTRING string); + using WindowsGetStringRawBufferType = PCWSTR(__stdcall*)(_In_opt_ HSTRING string, _Out_opt_ UINT32* length); using RoGetActivationFactoryType = HRESULT(__stdcall*)(HSTRING activatableClassId, REFIID iid, void** factory); using RoActivateInstanceType = HRESULT(__stdcall*)(HSTRING activatableClassId, IInspectable * *instance); using D2D1CreateFactoryType = HRESULT(__stdcall*)(_In_ D2D1_FACTORY_TYPE factoryType, _In_ REFIID riid, _In_opt_ CONST D2D1_FACTORY_OPTIONS * pFactoryOptions, _Out_ void** ppIFactory); extern CreateRandomAccessStreamOnFileType CreateRandomAccessStreamOnFile; extern WindowsCreateStringReferenceType WindowsCreateStringReference; + extern WindowsDeleteStringType WindowsDeleteString; + extern WindowsGetStringRawBufferType WindowsGetStringRawBuffer; extern RoGetActivationFactoryType RoGetActivationFactory; extern RoActivateInstanceType RoActivateInstance; extern D2D1CreateFactoryType D2D1CreateFactory; diff --git a/src/WinIMerge.cpp b/src/WinIMerge.cpp index 782fc2f..90f7f29 100644 --- a/src/WinIMerge.cpp +++ b/src/WinIMerge.cpp @@ -648,6 +648,27 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) case ID_VIEW_VECTORIMAGESCALING_25: m_pImgMergeWindow->SetVectorImageZoomRatio(static_cast(pow(2.0, (wmId - ID_VIEW_VECTORIMAGESCALING_100)))); break; + case ID_VIEW_EXTRACTTEXT: + { + int pane = m_pImgMergeWindow->GetActivePane(); + if (pane < 0) + pane = 0; + BSTR bstrText = m_pImgMergeWindow->ExtractTextFromImage( + pane, m_pImgMergeWindow->GetCurrentPage(pane), IImgMergeWindow::OCR_RESULT_TYPE::TEXT_PER_WORD_YAML); + MessageBoxW(nullptr, bstrText, L"", MB_OK); + SysFreeString(bstrText); + break; + } + case ID_VIEW_SHOWMETADATA: + { + int pane = m_pImgMergeWindow->GetActivePane(); + if (pane < 0) + pane = 0; + char buf[65536]; + m_pImgMergeWindow->GetMetadata(pane, buf, sizeof buf); + MessageBoxA(nullptr, buf, "", MB_OK); + break; + } case ID_MERGE_NEXTDIFFERENCE: m_pImgMergeWindow->NextDiff(); break; @@ -741,7 +762,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) case ID_HELP_ABOUT: MessageBoxW(hWnd, L"WinIMerge\n\n" - L"(c) 2014-2020 sdottaka@users.sourceforge.net All rights reserved.\n\n" + L"(c) 2014-2021 sdottaka@users.sourceforge.net All rights reserved.\n\n" L"This software uses the FreeImage open source image library. \n" L"See http://freeimage.sourceforge.net for details.\n" L"FreeImage is used under the GNU GPL version.\n", L"WinIMerge", MB_OK | MB_ICONINFORMATION); diff --git a/src/WinIMerge.rc b/src/WinIMerge.rc index 89d88e6..309e838 100644 --- a/src/WinIMerge.rc +++ b/src/WinIMerge.rc @@ -168,6 +168,9 @@ BEGIN MENUITEM "50%", ID_VIEW_VECTORIMAGESCALING_50 MENUITEM "25%", ID_VIEW_VECTORIMAGESCALING_25 END + MENUITEM SEPARATOR + MENUITEM "Extract &Text", ID_VIEW_EXTRACTTEXT + MENUITEM "Show &Metadata", ID_VIEW_SHOWMETADATA END POPUP "&Merge" BEGIN @@ -272,7 +275,7 @@ BEGIN VALUE "FileDescription", "WinIMerge" VALUE "FileVersion", STRFILEVER VALUE "InternalName", "WinIMerge.exe" - VALUE "LegalCopyright", "Copyright (C) 2014-2020 sdottaka@users.sourceforge.net" + VALUE "LegalCopyright", "Copyright (C) 2014-2021 sdottaka@users.sourceforge.net" VALUE "OriginalFilename", "WinIMerge.exe" VALUE "ProductName", "WinIMerge" VALUE "ProductVersion", STRPRODUCTVER diff --git a/src/WinIMergeLib.h b/src/WinIMergeLib.h index f14ff50..9c10fe4 100644 --- a/src/WinIMergeLib.h +++ b/src/WinIMergeLib.h @@ -18,6 +18,7 @@ #pragma once #include +#include struct IImgMergeWindow { @@ -40,6 +41,9 @@ struct IImgMergeWindow RECTANGLE_SELECT, MOVE_IMAGE = 256, RESIZE_WIDTH, RESIZE_HEIGHT, RESIZE_BOTH }; + enum OCR_RESULT_TYPE { + TEXT_ONLY = 0, TEXT_PER_LINE_YAML, TEXT_PER_WORD_YAML + }; struct Event { void *userdata; @@ -164,6 +168,7 @@ struct IImgMergeWindow virtual bool IsPastable() const = 0; virtual bool IsCancellable() const = 0; virtual bool IsRectangleSelectionVisible(int pane) const = 0; + virtual BSTR ExtractTextFromImage(int pane, int page, OCR_RESULT_TYPE resultType) = 0; }; struct IImgToolWindow diff --git a/src/WinIMergeLib.vs2017.vcxproj b/src/WinIMergeLib.vs2017.vcxproj index c018a45..ca67e89 100644 --- a/src/WinIMergeLib.vs2017.vcxproj +++ b/src/WinIMergeLib.vs2017.vcxproj @@ -221,6 +221,7 @@ + diff --git a/src/WinIMergeLib.vs2017.vcxproj.filters b/src/WinIMergeLib.vs2017.vcxproj.filters index 03f75b5..eb09624 100644 --- a/src/WinIMergeLib.vs2017.vcxproj.filters +++ b/src/WinIMergeLib.vs2017.vcxproj.filters @@ -45,6 +45,9 @@ Header Files + + Header Files + diff --git a/src/WinIMergeLib.vs2019.vcxproj b/src/WinIMergeLib.vs2019.vcxproj index b7a3338..e3db92e 100644 --- a/src/WinIMergeLib.vs2019.vcxproj +++ b/src/WinIMergeLib.vs2019.vcxproj @@ -221,6 +221,7 @@ + diff --git a/src/WinIMergeLib.vs2019.vcxproj.filters b/src/WinIMergeLib.vs2019.vcxproj.filters index 03f75b5..eb09624 100644 --- a/src/WinIMergeLib.vs2019.vcxproj.filters +++ b/src/WinIMergeLib.vs2019.vcxproj.filters @@ -45,6 +45,9 @@ Header Files + + Header Files + diff --git a/src/resource.h b/src/resource.h index fe17bdd..2a7adb4 100644 --- a/src/resource.h +++ b/src/resource.h @@ -133,6 +133,8 @@ #define ID_VIEW_VECTORIMAGESCALING_100 32862 #define ID_VIEW_VECTORIMAGESCALING_200 32863 #define ID_VIEW_VECTORIMAGESCALING_400 32864 +#define ID_VIEW_EXTRACTTEXT 32870 +#define ID_VIEW_SHOWMETADATA 32871 #define IDC_STATIC -1 // Next default values for new objects @@ -141,7 +143,7 @@ #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NO_MFC 1 #define _APS_NEXT_RESOURCE_VALUE 142 -#define _APS_NEXT_COMMAND_VALUE 32870 +#define _APS_NEXT_COMMAND_VALUE 32872 #define _APS_NEXT_CONTROL_VALUE 1026 #define _APS_NEXT_SYMED_VALUE 111 #endif