diff --git a/include/tesseract/baseapi.h b/include/tesseract/baseapi.h index 9475fb27ba..951a016b39 100644 --- a/include/tesseract/baseapi.h +++ b/include/tesseract/baseapi.h @@ -283,6 +283,17 @@ class TESS_API TessBaseAPI { int bytes_per_line, int left, int top, int width, int height); + /** + * Recognize each rectangle in a Boxa and concatenate the UTF-8 results in + * box order. SetImage must be called before this method. The caller owns + * the returned string and must free it with delete[]. + * + * This keeps the image and initialized engine shared while processing a + * collection of regions, which avoids duplicating the rectangle loop in + * applications that need OCR for multiple regions on one image. + */ + char *GetUTF8TextForBoxes(const Boxa *boxes); + /** * Call between pages or documents etc to free up memory and forget * adaptive data. diff --git a/include/tesseract/capi.h b/include/tesseract/capi.h index b1680bba2d..75d7236d98 100644 --- a/include/tesseract/capi.h +++ b/include/tesseract/capi.h @@ -386,6 +386,14 @@ TESS_API TessMutableIterator *TessBaseAPIGetMutableIterator( */ TESS_API char *TessBaseAPIGetUTF8Text(TessBaseAPI *handle); +/** + * Recognizes each rectangle in a Boxa and concatenates the UTF-8 results in + * box order. The caller is responsible for freeing the returned string using + * TessDeleteText(). + */ +TESS_API char *TessBaseAPIGetUTF8TextForBoxes(TessBaseAPI *handle, + const struct Boxa *boxes); + /** * Returns the HOCR text for the page. * diff --git a/src/api/baseapi.cpp b/src/api/baseapi.cpp index 0c1628376c..ea27d15370 100644 --- a/src/api/baseapi.cpp +++ b/src/api/baseapi.cpp @@ -480,6 +480,32 @@ char *TessBaseAPI::TesseractRect(const unsigned char *imagedata, int bytes_per_p return GetUTF8Text(); } +char *TessBaseAPI::GetUTF8TextForBoxes(const Boxa *boxes) { + if (tesseract_ == nullptr || thresholder_ == nullptr || boxes == nullptr) { + return nullptr; + } + + std::string text; + const int box_count = boxaGetCount(const_cast(boxes)); + for (int i = 0; i < box_count; ++i) { + int left; + int top; + int width; + int height; + boxaGetBoxGeometry(const_cast(boxes), i, &left, &top, &width, &height); + if (width <= 0 || height <= 0) { + continue; + } + SetRectangle(left, top, width, height); + char *box_text = GetUTF8Text(); + if (box_text != nullptr) { + text += box_text; + delete[] box_text; + } + } + return copy_string(text); +} + #ifndef DISABLED_LEGACY_ENGINE /** * Call between pages or documents etc to free up memory and forget diff --git a/src/api/capi.cpp b/src/api/capi.cpp index 445f0832da..44d8d916d4 100644 --- a/src/api/capi.cpp +++ b/src/api/capi.cpp @@ -420,6 +420,10 @@ char *TessBaseAPIGetUTF8Text(TessBaseAPI *handle) { return handle->GetUTF8Text(); } +char *TessBaseAPIGetUTF8TextForBoxes(TessBaseAPI *handle, const struct Boxa *boxes) { + return handle->GetUTF8TextForBoxes(boxes); +} + char *TessBaseAPIGetHOCRText(TessBaseAPI *handle, int page_number) { return handle->GetHOCRText(nullptr, page_number); } diff --git a/unittest/baseapi_test.cc b/unittest/baseapi_test.cc index 9336b5adfa..ccdb22dadd 100644 --- a/unittest/baseapi_test.cc +++ b/unittest/baseapi_test.cc @@ -88,6 +88,34 @@ TEST_F(TesseractTest, BasicTesseractTest) { } } +// Test that OCR results from multiple regions are returned in box order. +TEST_F(TesseractTest, UTF8TextForBoxes) { + tesseract::TessBaseAPI api; + if (api.Init(TessdataPath().c_str(), "eng", tesseract::OEM_LSTM_ONLY) == -1) { + // eng.traineddata not found. + GTEST_SKIP(); + } + Image src_pix = pixRead(TestDataNameToPath("HelloGoogle.tif").c_str()); + CHECK(src_pix); + api.SetImage(src_pix); + + const int width = pixGetWidth(src_pix); + const int height = pixGetHeight(src_pix); + Boxa *boxes = boxaCreate(2); + boxaAddBox(boxes, boxCreate(0, 0, width, height), L_INSERT); + boxaAddBox(boxes, boxCreate(0, 0, width, height), L_INSERT); + + const std::unique_ptr combined(api.GetUTF8TextForBoxes(boxes)); + ASSERT_NE(combined, nullptr); + api.SetRectangle(0, 0, width, height); + const std::unique_ptr single(api.GetUTF8Text()); + ASSERT_NE(single, nullptr); + EXPECT_EQ(std::string(single.get()) + single.get(), combined.get()); + + boxaDestroy(&boxes); + src_pix.destroy(); +} + // Test that api.GetComponentImages() will return a set of images for // paragraphs even if text recognition was not run. TEST_F(TesseractTest, IteratesParagraphsEvenIfNotDetected) {