Commit 66389e5a for tesseract
commit 66389e5a709f14e45cbb6191cd7e37e8d7919867
Author: Stefan Weil <sw@weilnetz.de>
Date: Fri Nov 13 17:31:00 2026 +0100
textord: skip the distance transform for blobs with a huge box
SetBlobStrokeWidth() clips the page to the bounding box of the blob
and runs pixDistanceFunction() and two scans over it, so the cost is
the area of the box, however small the blob is. A page with thin
nested rings or diagonal lines has hundreds of blobs with boxes of
nearly the size of the page, and the layout analysis took minutes.
Store zero widths if the area of the box is more than 128 times the
perimeter of the outline. Zero is what the function already stores
for a blob with not enough samples; users of the widths then use the
estimate from area and perimeter.
This changes the stroke widths of blobs like page-sized frames or scan
borders, of long thin lines and of very large glyphs.
The limit of 128 was tested with 237 pages of books, journals and
letters and with 101 pages of a newspaper (Deutscher Reichsanzeiger).
182 of these pages have blobs above the limit, mostly a single blob
which spans the whole page. For all pages of the first set and for
the 23 pages of the second set which have such blobs, the text and
hOCR output with model german_print2_15 and --psm 3 was identical
with and without this change, and the run time did not change
noticeably. A synthetic page of 2000 x 2000 pixels with nested rings
needs 4.3 s instead of 18.7 s.
Fix proposed by the Anthropic OSS Scanner (finding ANT-2026-LZYJKPKM).
Assisted-by: OpenCode / Claude Fable 5.2 (Anthropic)
Signed-off-by: Stefan Weil <sw@weilnetz.de>
diff --git a/src/textord/tordmain.cpp b/src/textord/tordmain.cpp
index 69222cca..778711eb 100644
--- a/src/textord/tordmain.cpp
+++ b/src/textord/tordmain.cpp
@@ -58,6 +58,10 @@ namespace tesseract {
#define MAX_NEAREST_DIST 600 // for block skew stats
+// Max area of the bounding box of a blob, per unit of outline perimeter,
+// for which SetBlobStrokeWidth computes the distance transform.
+const int kMaxBoxAreaPerPerimeter = 128;
+
/**********************************************************************
* SetBlobStrokeWidth
*
@@ -69,6 +73,18 @@ void SetBlobStrokeWidth(Image pix, BLOBNBOX *blob) {
const TBOX &box = blob->bounding_box();
int width = box.width();
int height = box.height();
+ // The distance transform and the scans below cost the area of the box,
+ // not the size of the blob. A thin blob whose box is out of proportion to
+ // its outline (eg a ring or a diagonal line across the page) can make that
+ // cost the whole page for a handful of pixels, so store zero widths
+ // instead, which makes the users fall back on the area/perimeter estimate.
+ if (blob->cblob() != nullptr &&
+ static_cast<int64_t>(width) * height >
+ static_cast<int64_t>(kMaxBoxAreaPerPerimeter) * blob->cblob()->perimeter()) {
+ blob->set_horz_stroke_width(0.0f);
+ blob->set_vert_stroke_width(0.0f);
+ return;
+ }
Box *blob_pix_box = boxCreate(box.left(), pix_height - box.top(), width, height);
Image pix_blob = pixClipRectangle(pix, blob_pix_box, nullptr);
boxDestroy(&blob_pix_box);