/src/tesseract/src/ccmain/pageiterator.cpp
Line | Count | Source |
1 | | /////////////////////////////////////////////////////////////////////// |
2 | | // File: pageiterator.cpp |
3 | | // Description: Iterator for tesseract page structure that avoids using |
4 | | // tesseract internal data structures. |
5 | | // Author: Ray Smith |
6 | | // |
7 | | // (C) Copyright 2010, Google Inc. |
8 | | // Licensed under the Apache License, Version 2.0 (the "License"); |
9 | | // you may not use this file except in compliance with the License. |
10 | | // You may obtain a copy of the License at |
11 | | // http://www.apache.org/licenses/LICENSE-2.0 |
12 | | // Unless required by applicable law or agreed to in writing, software |
13 | | // distributed under the License is distributed on an "AS IS" BASIS, |
14 | | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
15 | | // See the License for the specific language governing permissions and |
16 | | // limitations under the License. |
17 | | // |
18 | | /////////////////////////////////////////////////////////////////////// |
19 | | |
20 | | #include <tesseract/pageiterator.h> |
21 | | #include "helpers.h" |
22 | | #include "image.h" // for Image |
23 | | #include "pageres.h" |
24 | | #include "tesseractclass.h" |
25 | | |
26 | | #include <algorithm> |
27 | | |
28 | | namespace tesseract { |
29 | | |
30 | | PageIterator::PageIterator(PAGE_RES *page_res, Tesseract *tesseract, int scale, |
31 | | int scaled_yres, int rect_left, int rect_top, |
32 | | int rect_width, int rect_height) |
33 | 30.0k | : page_res_(page_res), |
34 | 30.0k | tesseract_(tesseract), |
35 | 30.0k | word_(nullptr), |
36 | 30.0k | word_length_(0), |
37 | 30.0k | blob_index_(0), |
38 | 30.0k | cblob_it_(nullptr), |
39 | 30.0k | include_upper_dots_(false), |
40 | 30.0k | include_lower_dots_(false), |
41 | 30.0k | scale_(scale), |
42 | 30.0k | scaled_yres_(scaled_yres), |
43 | 30.0k | rect_left_(rect_left), |
44 | 30.0k | rect_top_(rect_top), |
45 | 30.0k | rect_width_(rect_width), |
46 | 30.0k | rect_height_(rect_height) { |
47 | 30.0k | it_ = new PAGE_RES_IT(page_res); |
48 | 30.0k | PageIterator::Begin(); |
49 | 30.0k | } |
50 | | |
51 | 4.46M | PageIterator::~PageIterator() { |
52 | 4.46M | delete it_; |
53 | 4.46M | delete cblob_it_; |
54 | 4.46M | } |
55 | | |
56 | | /** |
57 | | * PageIterators may be copied! This makes it possible to iterate over |
58 | | * all the objects at a lower level, while maintaining an iterator to |
59 | | * objects at a higher level. |
60 | | */ |
61 | | PageIterator::PageIterator(const PageIterator &src) |
62 | 4.43M | : page_res_(src.page_res_), |
63 | 4.43M | tesseract_(src.tesseract_), |
64 | 4.43M | word_(nullptr), |
65 | 4.43M | word_length_(src.word_length_), |
66 | 4.43M | blob_index_(src.blob_index_), |
67 | 4.43M | cblob_it_(nullptr), |
68 | 4.43M | include_upper_dots_(src.include_upper_dots_), |
69 | 4.43M | include_lower_dots_(src.include_lower_dots_), |
70 | 4.43M | scale_(src.scale_), |
71 | 4.43M | scaled_yres_(src.scaled_yres_), |
72 | 4.43M | rect_left_(src.rect_left_), |
73 | 4.43M | rect_top_(src.rect_top_), |
74 | 4.43M | rect_width_(src.rect_width_), |
75 | 4.43M | rect_height_(src.rect_height_) { |
76 | 4.43M | it_ = new PAGE_RES_IT(*src.it_); |
77 | 4.43M | BeginWord(src.blob_index_); |
78 | 4.43M | } |
79 | | |
80 | 14.2k | const PageIterator &PageIterator::operator=(const PageIterator &src) { |
81 | 14.2k | page_res_ = src.page_res_; |
82 | 14.2k | tesseract_ = src.tesseract_; |
83 | 14.2k | include_upper_dots_ = src.include_upper_dots_; |
84 | 14.2k | include_lower_dots_ = src.include_lower_dots_; |
85 | 14.2k | scale_ = src.scale_; |
86 | 14.2k | scaled_yres_ = src.scaled_yres_; |
87 | 14.2k | rect_left_ = src.rect_left_; |
88 | 14.2k | rect_top_ = src.rect_top_; |
89 | 14.2k | rect_width_ = src.rect_width_; |
90 | 14.2k | rect_height_ = src.rect_height_; |
91 | 14.2k | delete it_; |
92 | 14.2k | it_ = new PAGE_RES_IT(*src.it_); |
93 | 14.2k | BeginWord(src.blob_index_); |
94 | 14.2k | return *this; |
95 | 14.2k | } |
96 | | |
97 | 1.07M | bool PageIterator::PositionedAtSameWord(const PAGE_RES_IT *other) const { |
98 | 1.07M | return (it_ == nullptr && it_ == other) || |
99 | 1.07M | ((other != nullptr) && (it_ != nullptr) && (*it_ == *other)); |
100 | 1.07M | } |
101 | | |
102 | | // ============= Moving around within the page ============. |
103 | | |
104 | | /** Resets the iterator to point to the start of the page. */ |
105 | 30.0k | void PageIterator::Begin() { |
106 | 30.0k | it_->restart_page_with_empties(); |
107 | 30.0k | BeginWord(0); |
108 | 30.0k | } |
109 | | |
110 | 21.1k | void PageIterator::RestartParagraph() { |
111 | 21.1k | if (it_->block() == nullptr) { |
112 | 0 | return; // At end of the document. |
113 | 0 | } |
114 | 21.1k | const BLOCK_RES *target_block = it_->block(); |
115 | 21.1k | const PARA *target_para = it_->row() == nullptr ? nullptr : it_->row()->row->para(); |
116 | 21.1k | PAGE_RES_IT para(page_res_); |
117 | | // Paragraph and block identities are stable within PAGE_RES. Avoid cmp(), |
118 | | // which searches the page and makes this forward scan quadratic. |
119 | 52.5k | while (para.block() != nullptr && |
120 | 52.5k | (para.block() != target_block || |
121 | 52.5k | (para.row() != nullptr && para.row()->row->para() != target_para))) { |
122 | 31.3k | para.forward_paragraph(); |
123 | 31.3k | } |
124 | 21.1k | if (para.block() != nullptr) { |
125 | 21.1k | *it_ = para; |
126 | 21.1k | BeginWord(0); |
127 | 21.1k | } |
128 | 21.1k | } |
129 | | |
130 | 0 | bool PageIterator::IsWithinFirstTextlineOfParagraph() const { |
131 | 0 | PageIterator p_start(*this); |
132 | 0 | p_start.RestartParagraph(); |
133 | 0 | return p_start.it_->row() == it_->row(); |
134 | 0 | } |
135 | | |
136 | 1.49M | void PageIterator::RestartRow() { |
137 | 1.49M | it_->restart_row(); |
138 | 1.49M | BeginWord(0); |
139 | 1.49M | } |
140 | | |
141 | | /** |
142 | | * Moves to the start of the next object at the given level in the |
143 | | * page hierarchy, and returns false if the end of the page was reached. |
144 | | * NOTE (CHANGED!) that ALL PageIteratorLevel level values will visit each |
145 | | * non-text block at least once. |
146 | | * Think of non text blocks as containing a single para, with at least one |
147 | | * line, with a single imaginary word, containing a single symbol. |
148 | | * The bounding boxes mark out any polygonal nature of the block, and |
149 | | * PTIsTextType(BLockType()) is false for non-text blocks. |
150 | | * Calls to Next with different levels may be freely intermixed. |
151 | | * This function iterates words in right-to-left scripts correctly, if |
152 | | * the appropriate language has been loaded into Tesseract. |
153 | | */ |
154 | 5.92M | bool PageIterator::Next(PageIteratorLevel level) { |
155 | 5.92M | if (it_->block() == nullptr) { |
156 | 0 | return false; // Already at the end! |
157 | 0 | } |
158 | 5.92M | if (it_->word() == nullptr) { |
159 | 0 | level = RIL_BLOCK; |
160 | 0 | } |
161 | | |
162 | 5.92M | switch (level) { |
163 | 14.2k | case RIL_BLOCK: |
164 | 14.2k | it_->forward_block(); |
165 | 14.2k | break; |
166 | 21.1k | case RIL_PARA: |
167 | 21.1k | it_->forward_paragraph(); |
168 | 21.1k | break; |
169 | 692k | case RIL_TEXTLINE: |
170 | 795k | for (it_->forward_with_empties(); it_->row() == it_->prev_row(); |
171 | 692k | it_->forward_with_empties()) { |
172 | 103k | ; |
173 | 103k | } |
174 | 692k | break; |
175 | 5.20M | case RIL_WORD: |
176 | 5.20M | it_->forward_with_empties(); |
177 | 5.20M | break; |
178 | 0 | case RIL_SYMBOL: |
179 | 0 | if (cblob_it_ != nullptr) { |
180 | 0 | cblob_it_->forward(); |
181 | 0 | } |
182 | 0 | ++blob_index_; |
183 | 0 | if (blob_index_ >= word_length_) { |
184 | 0 | it_->forward_with_empties(); |
185 | 0 | } else { |
186 | 0 | return true; |
187 | 0 | } |
188 | 0 | break; |
189 | 5.92M | } |
190 | 5.92M | BeginWord(0); |
191 | 5.92M | return it_->block() != nullptr; |
192 | 5.92M | } |
193 | | |
194 | | /** |
195 | | * Returns true if the iterator is at the start of an object at the given |
196 | | * level. Possible uses include determining if a call to Next(RIL_WORD) |
197 | | * moved to the start of a RIL_PARA. |
198 | | */ |
199 | 9.26M | bool PageIterator::IsAtBeginningOf(PageIteratorLevel level) const { |
200 | 9.26M | if (it_->block() == nullptr) { |
201 | 3.28k | return false; // Already at the end! |
202 | 3.28k | } |
203 | 9.26M | if (it_->word() == nullptr) { |
204 | 0 | return true; // In an image block. |
205 | 0 | } |
206 | 9.26M | switch (level) { |
207 | 0 | case RIL_BLOCK: |
208 | 0 | return blob_index_ == 0 && it_->block() != it_->prev_block(); |
209 | 1.46M | case RIL_PARA: |
210 | 1.46M | return blob_index_ == 0 && |
211 | 1.46M | (it_->block() != it_->prev_block() || |
212 | 1.43M | it_->row()->row->para() != it_->prev_row()->row->para()); |
213 | 7.79M | case RIL_TEXTLINE: |
214 | 7.79M | return blob_index_ == 0 && it_->row() != it_->prev_row(); |
215 | 0 | case RIL_WORD: |
216 | 0 | return blob_index_ == 0; |
217 | 0 | case RIL_SYMBOL: |
218 | 0 | return true; |
219 | 9.26M | } |
220 | 0 | return false; |
221 | 9.26M | } |
222 | | |
223 | | /** |
224 | | * Returns whether the iterator is positioned at the last element in a |
225 | | * given level. (e.g. the last word in a line, the last line in a block) |
226 | | */ |
227 | | bool PageIterator::IsAtFinalElement(PageIteratorLevel level, |
228 | 0 | PageIteratorLevel element) const { |
229 | 0 | if (Empty(element)) { |
230 | 0 | return true; // Already at the end! |
231 | 0 | } |
232 | | // The result is true if we step forward by element and find we are |
233 | | // at the end of the page or at beginning of *all* levels in: |
234 | | // [level, element). |
235 | | // When there is more than one level difference between element and level, |
236 | | // we could for instance move forward one symbol and still be at the first |
237 | | // word on a line, so we also have to be at the first symbol in a word. |
238 | 0 | PageIterator next(*this); |
239 | 0 | next.Next(element); |
240 | 0 | if (next.Empty(element)) { |
241 | 0 | return true; // Reached the end of the page. |
242 | 0 | } |
243 | 0 | while (element > level) { |
244 | 0 | element = static_cast<PageIteratorLevel>(element - 1); |
245 | 0 | if (!next.IsAtBeginningOf(element)) { |
246 | 0 | return false; |
247 | 0 | } |
248 | 0 | } |
249 | 0 | return true; |
250 | 0 | } |
251 | | |
252 | | /** |
253 | | * Returns whether this iterator is positioned |
254 | | * before other: -1 |
255 | | * equal to other: 0 |
256 | | * after other: 1 |
257 | | */ |
258 | 0 | int PageIterator::Cmp(const PageIterator &other) const { |
259 | 0 | int word_cmp = it_->cmp(*other.it_); |
260 | 0 | if (word_cmp != 0) { |
261 | 0 | return word_cmp; |
262 | 0 | } |
263 | 0 | if (blob_index_ < other.blob_index_) { |
264 | 0 | return -1; |
265 | 0 | } |
266 | 0 | if (blob_index_ == other.blob_index_) { |
267 | 0 | return 0; |
268 | 0 | } |
269 | 0 | return 1; |
270 | 0 | } |
271 | | |
272 | | // ============= Accessing data ==============. |
273 | | // Coordinate system: |
274 | | // Integer coordinates are at the cracks between the pixels. |
275 | | // The top-left corner of the top-left pixel in the image is at (0,0). |
276 | | // The bottom-right corner of the bottom-right pixel in the image is at |
277 | | // (width, height). |
278 | | // Every bounding box goes from the top-left of the top-left contained |
279 | | // pixel to the bottom-right of the bottom-right contained pixel, so |
280 | | // the bounding box of the single top-left pixel in the image is: |
281 | | // (0,0)->(1,1). |
282 | | // If an image rectangle has been set in the API, then returned coordinates |
283 | | // relate to the original (full) image, rather than the rectangle. |
284 | | |
285 | | /** |
286 | | * Returns the bounding rectangle of the current object at the given level in |
287 | | * the coordinates of the working image that is pix_binary(). |
288 | | * See comment on coordinate system above. |
289 | | * Returns false if there is no such object at the current position. |
290 | | */ |
291 | | bool PageIterator::BoundingBoxInternal(PageIteratorLevel level, int *left, |
292 | | int *top, int *right, |
293 | 0 | int *bottom) const { |
294 | 0 | if (Empty(level)) { |
295 | 0 | return false; |
296 | 0 | } |
297 | 0 | TBOX box; |
298 | 0 | PARA *para = nullptr; |
299 | 0 | switch (level) { |
300 | 0 | case RIL_BLOCK: |
301 | 0 | box = it_->block()->block->restricted_bounding_box(include_upper_dots_, |
302 | 0 | include_lower_dots_); |
303 | 0 | break; |
304 | 0 | case RIL_PARA: |
305 | 0 | para = it_->row()->row->para(); |
306 | | // For a paragraph the box is the union of the full bounding boxes of |
307 | | // all its rows. Do not seed with the current row's restricted box: the |
308 | | // restricted box excludes upper/lower dots, so the result would depend |
309 | | // on which row the iterator currently sits on. |
310 | 0 | break; |
311 | 0 | case RIL_TEXTLINE: |
312 | 0 | box = it_->row()->row->restricted_bounding_box(include_upper_dots_, |
313 | 0 | include_lower_dots_); |
314 | 0 | break; |
315 | 0 | case RIL_WORD: |
316 | 0 | box = it_->word()->word->restricted_bounding_box(include_upper_dots_, |
317 | 0 | include_lower_dots_); |
318 | 0 | break; |
319 | 0 | case RIL_SYMBOL: |
320 | 0 | if (cblob_it_ == nullptr) { |
321 | 0 | box = it_->word()->box_word->BlobBox(blob_index_); |
322 | 0 | } else { |
323 | 0 | box = cblob_it_->data()->bounding_box(); |
324 | 0 | } |
325 | 0 | } |
326 | 0 | if (level == RIL_PARA) { |
327 | 0 | PageIterator other = *this; |
328 | 0 | other.Begin(); |
329 | 0 | do { |
330 | 0 | if (other.it_->block() && |
331 | 0 | other.it_->block()->block == it_->block()->block && |
332 | 0 | other.it_->row() && other.it_->row()->row && |
333 | 0 | other.it_->row()->row->para() == para) { |
334 | 0 | box = box.bounding_union(other.it_->row()->row->bounding_box()); |
335 | 0 | } |
336 | 0 | } while (other.Next(RIL_TEXTLINE)); |
337 | 0 | } |
338 | 0 | if (level != RIL_SYMBOL || cblob_it_ != nullptr) { |
339 | 0 | box.rotate(it_->block()->block->re_rotation()); |
340 | 0 | } |
341 | | // Now we have a box in tesseract coordinates relative to the image rectangle, |
342 | | // we have to convert the coords to a top-down system. |
343 | 0 | const int pix_height = pixGetHeight(tesseract_->pix_binary()); |
344 | 0 | const int pix_width = pixGetWidth(tesseract_->pix_binary()); |
345 | 0 | *left = ClipToRange(static_cast<int>(box.left()), 0, pix_width); |
346 | 0 | *top = ClipToRange(pix_height - box.top(), 0, pix_height); |
347 | 0 | *right = ClipToRange(static_cast<int>(box.right()), *left, pix_width); |
348 | 0 | *bottom = ClipToRange(pix_height - box.bottom(), *top, pix_height); |
349 | 0 | return true; |
350 | 0 | } |
351 | | |
352 | | /** |
353 | | * Returns the bounding rectangle of the current object at the given level in |
354 | | * coordinates of the original image. |
355 | | * See comment on coordinate system above. |
356 | | * Returns false if there is no such object at the current position. |
357 | | */ |
358 | | bool PageIterator::BoundingBox(PageIteratorLevel level, int *left, int *top, |
359 | 0 | int *right, int *bottom) const { |
360 | 0 | return BoundingBox(level, 0, left, top, right, bottom); |
361 | 0 | } |
362 | | |
363 | | bool PageIterator::BoundingBox(PageIteratorLevel level, const int padding, |
364 | | int *left, int *top, int *right, |
365 | 0 | int *bottom) const { |
366 | 0 | if (!BoundingBoxInternal(level, left, top, right, bottom)) { |
367 | 0 | return false; |
368 | 0 | } |
369 | | // Convert to the coordinate system of the original image. |
370 | 0 | *left = ClipToRange(*left / scale_ + rect_left_ - padding, rect_left_, |
371 | 0 | rect_left_ + rect_width_); |
372 | 0 | *top = ClipToRange(*top / scale_ + rect_top_ - padding, rect_top_, |
373 | 0 | rect_top_ + rect_height_); |
374 | 0 | *right = ClipToRange((*right + scale_ - 1) / scale_ + rect_left_ + padding, |
375 | 0 | *left, rect_left_ + rect_width_); |
376 | 0 | *bottom = ClipToRange((*bottom + scale_ - 1) / scale_ + rect_top_ + padding, |
377 | 0 | *top, rect_top_ + rect_height_); |
378 | 0 | return true; |
379 | 0 | } |
380 | | |
381 | | /** Return that there is no such object at a given level. */ |
382 | 6.21M | bool PageIterator::Empty(PageIteratorLevel level) const { |
383 | 6.21M | if (it_->block() == nullptr) { |
384 | 42.3k | return true; // Already at the end! |
385 | 42.3k | } |
386 | 6.17M | if (it_->word() == nullptr && level != RIL_BLOCK) { |
387 | 0 | return true; // image block |
388 | 0 | } |
389 | 6.17M | if (level == RIL_SYMBOL && blob_index_ >= word_length_) { |
390 | 0 | return true; // Zero length word, or already at the end of it. |
391 | 0 | } |
392 | 6.17M | return false; |
393 | 6.17M | } |
394 | | |
395 | | /** Returns the type of the current block. |
396 | | * See tesseract/publictypes.h for PolyBlockType. */ |
397 | 21.1k | PolyBlockType PageIterator::BlockType() const { |
398 | 21.1k | if (it_->block() == nullptr || it_->block()->block == nullptr) { |
399 | 0 | return PT_UNKNOWN; // Already at the end! |
400 | 0 | } |
401 | 21.1k | if (it_->block()->block->pdblk.poly_block() == nullptr) { |
402 | 21.1k | return PT_FLOWING_TEXT; // No layout analysis used - assume text. |
403 | 21.1k | } |
404 | 0 | return it_->block()->block->pdblk.poly_block()->isA(); |
405 | 21.1k | } |
406 | | |
407 | | /** Returns the polygon outline of the current block. The returned Pta must |
408 | | * be ptaDestroy-ed after use. */ |
409 | 0 | Pta *PageIterator::BlockPolygon() const { |
410 | 0 | if (it_->block() == nullptr || it_->block()->block == nullptr) { |
411 | 0 | return nullptr; // Already at the end! |
412 | 0 | } |
413 | 0 | if (it_->block()->block->pdblk.poly_block() == nullptr) { |
414 | 0 | return nullptr; // No layout analysis used - no polygon. |
415 | 0 | } |
416 | | // Copy polygon, so we can unrotate it to image coordinates. |
417 | 0 | POLY_BLOCK *internal_poly = it_->block()->block->pdblk.poly_block(); |
418 | 0 | ICOORDELT_LIST vertices; |
419 | 0 | vertices.deep_copy(internal_poly->points(), ICOORDELT::deep_copy); |
420 | 0 | POLY_BLOCK poly(&vertices, internal_poly->isA()); |
421 | 0 | poly.rotate(it_->block()->block->re_rotation()); |
422 | 0 | ICOORDELT_IT it(poly.points()); |
423 | 0 | Pta *pta = ptaCreate(it.length()); |
424 | 0 | int num_pts = 0; |
425 | 0 | for (it.mark_cycle_pt(); !it.cycled_list(); it.forward(), ++num_pts) { |
426 | 0 | ICOORD *pt = it.data(); |
427 | | // Convert to top-down coords within the input image. |
428 | 0 | int x = static_cast<float>(pt->x()) / scale_ + rect_left_; |
429 | 0 | int y = rect_top_ + rect_height_ - static_cast<float>(pt->y()) / scale_; |
430 | 0 | x = ClipToRange(x, rect_left_, rect_left_ + rect_width_); |
431 | 0 | y = ClipToRange(y, rect_top_, rect_top_ + rect_height_); |
432 | 0 | ptaAddPt(pta, x, y); |
433 | 0 | } |
434 | 0 | return pta; |
435 | 0 | } |
436 | | |
437 | | /** |
438 | | * Returns a binary image of the current object at the given level. |
439 | | * The position and size match the return from BoundingBoxInternal, and so this |
440 | | * could be upscaled with respect to the original input image. |
441 | | * Use pixDestroy to delete the image after use. |
442 | | * The following methods are used to generate the images: |
443 | | * RIL_BLOCK: mask the page image with the block polygon. |
444 | | * RIL_TEXTLINE: Clip the rectangle of the line box from the page image. |
445 | | * TODO(rays) fix this to generate and use a line polygon. |
446 | | * RIL_WORD: Clip the rectangle of the word box from the page image. |
447 | | * RIL_SYMBOL: Render the symbol outline to an image for cblobs (prior |
448 | | * to recognition) or the bounding box otherwise. |
449 | | * A reconstruction of the original image (using xor to check for double |
450 | | * representation) should be reasonably accurate, |
451 | | * apart from removed noise, at the block level. Below the block level, the |
452 | | * reconstruction will be missing images and line separators. |
453 | | * At the symbol level, kerned characters will be invade the bounding box |
454 | | * if rendered after recognition, making an xor reconstruction inaccurate, but |
455 | | * an or construction better. Before recognition, symbol-level reconstruction |
456 | | * should be good, even with xor, since the images come from the connected |
457 | | * components. |
458 | | */ |
459 | 0 | Pix *PageIterator::GetBinaryImage(PageIteratorLevel level) const { |
460 | 0 | int left, top, right, bottom; |
461 | 0 | if (!BoundingBoxInternal(level, &left, &top, &right, &bottom)) { |
462 | 0 | return nullptr; |
463 | 0 | } |
464 | 0 | if (level == RIL_SYMBOL && cblob_it_ != nullptr && |
465 | 0 | cblob_it_->data()->area() != 0) { |
466 | 0 | return cblob_it_->data()->render(); |
467 | 0 | } |
468 | 0 | Box *box = boxCreate(left, top, right - left, bottom - top); |
469 | 0 | Image pix = pixClipRectangle(tesseract_->pix_binary(), box, nullptr); |
470 | 0 | boxDestroy(&box); |
471 | 0 | if (level == RIL_BLOCK || level == RIL_PARA) { |
472 | | // Clip to the block polygon as well. |
473 | 0 | TBOX mask_box; |
474 | 0 | Image mask = it_->block()->block->render_mask(&mask_box); |
475 | 0 | int mask_x = left - mask_box.left(); |
476 | 0 | int mask_y = top - (tesseract_->ImageHeight() - mask_box.top()); |
477 | | // AND the mask and pix, putting the result in pix. |
478 | 0 | pixRasterop(pix, std::max(0, -mask_x), std::max(0, -mask_y), |
479 | 0 | pixGetWidth(pix), pixGetHeight(pix), PIX_SRC & PIX_DST, mask, |
480 | 0 | std::max(0, mask_x), std::max(0, mask_y)); |
481 | 0 | mask.destroy(); |
482 | 0 | } |
483 | 0 | return pix; |
484 | 0 | } |
485 | | |
486 | | /** |
487 | | * Returns an image of the current object at the given level in greyscale |
488 | | * if available in the input. To guarantee a binary image use BinaryImage. |
489 | | * NOTE that in order to give the best possible image, the bounds are |
490 | | * expanded slightly over the binary connected component, by the supplied |
491 | | * padding, so the top-left position of the returned image is returned |
492 | | * in (left,top). These will most likely not match the coordinates |
493 | | * returned by BoundingBox. |
494 | | * If you do not supply an original image, you will get a binary one. |
495 | | * Use pixDestroy to delete the image after use. |
496 | | */ |
497 | | Pix *PageIterator::GetImage(PageIteratorLevel level, int padding, |
498 | 0 | Pix *original_img, int *left, int *top) const { |
499 | 0 | int right, bottom; |
500 | 0 | if (!BoundingBox(level, left, top, &right, &bottom)) { |
501 | 0 | return nullptr; |
502 | 0 | } |
503 | 0 | if (original_img == nullptr) { |
504 | 0 | return GetBinaryImage(level); |
505 | 0 | } |
506 | | |
507 | | // Expand the box. |
508 | 0 | *left = std::max(*left - padding, 0); |
509 | 0 | *top = std::max(*top - padding, 0); |
510 | 0 | right = std::min(right + padding, rect_width_); |
511 | 0 | bottom = std::min(bottom + padding, rect_height_); |
512 | 0 | Box *box = boxCreate(*left, *top, right - *left, bottom - *top); |
513 | 0 | Image grey_pix = pixClipRectangle(original_img, box, nullptr); |
514 | 0 | boxDestroy(&box); |
515 | 0 | if (level == RIL_BLOCK || level == RIL_PARA) { |
516 | | // Clip to the block polygon as well. |
517 | 0 | TBOX mask_box; |
518 | 0 | Image mask = it_->block()->block->render_mask(&mask_box); |
519 | | // Copy the mask registered correctly into an image the size of grey_pix. |
520 | 0 | int mask_x = *left - mask_box.left(); |
521 | 0 | int mask_y = *top - (pixGetHeight(original_img) - mask_box.top()); |
522 | 0 | int width = pixGetWidth(grey_pix); |
523 | 0 | int height = pixGetHeight(grey_pix); |
524 | 0 | Image resized_mask = pixCreate(width, height, 1); |
525 | 0 | pixRasterop(resized_mask, std::max(0, -mask_x), std::max(0, -mask_y), width, |
526 | 0 | height, PIX_SRC, mask, std::max(0, mask_x), |
527 | 0 | std::max(0, mask_y)); |
528 | 0 | mask.destroy(); |
529 | 0 | pixDilateBrick(resized_mask, resized_mask, 2 * padding + 1, |
530 | 0 | 2 * padding + 1); |
531 | 0 | pixInvert(resized_mask, resized_mask); |
532 | 0 | pixSetMasked(grey_pix, resized_mask, UINT32_MAX); |
533 | 0 | resized_mask.destroy(); |
534 | 0 | } |
535 | 0 | return grey_pix; |
536 | 0 | } |
537 | | |
538 | | /** |
539 | | * Returns the baseline of the current object at the given level. |
540 | | * The baseline is the line that passes through (x1, y1) and (x2, y2). |
541 | | * WARNING: with vertical text, baselines may be vertical! |
542 | | */ |
543 | | bool PageIterator::Baseline(PageIteratorLevel level, int *x1, int *y1, int *x2, |
544 | 0 | int *y2) const { |
545 | 0 | if (it_->word() == nullptr) { |
546 | 0 | return false; // Already at the end! |
547 | 0 | } |
548 | 0 | ROW *row = it_->row()->row; |
549 | 0 | WERD *word = it_->word()->word; |
550 | 0 | TBOX box = (level == RIL_WORD || level == RIL_SYMBOL) ? word->bounding_box() |
551 | 0 | : row->bounding_box(); |
552 | 0 | int left = box.left(); |
553 | 0 | ICOORD startpt(left, static_cast<int16_t>(row->base_line(left) + 0.5)); |
554 | 0 | int right = box.right(); |
555 | 0 | ICOORD endpt(right, static_cast<int16_t>(row->base_line(right) + 0.5)); |
556 | | // Rotate to image coordinates and convert to global image coords. |
557 | 0 | startpt.rotate(it_->block()->block->re_rotation()); |
558 | 0 | endpt.rotate(it_->block()->block->re_rotation()); |
559 | 0 | *x1 = startpt.x() / scale_ + rect_left_; |
560 | 0 | *y1 = (rect_height_ - startpt.y()) / scale_ + rect_top_; |
561 | 0 | *x2 = endpt.x() / scale_ + rect_left_; |
562 | 0 | *y2 = (rect_height_ - endpt.y()) / scale_ + rect_top_; |
563 | 0 | return true; |
564 | 0 | } |
565 | | |
566 | | void PageIterator::RowAttributes(float *row_height, float *descenders, |
567 | 0 | float *ascenders) const { |
568 | 0 | *row_height = it_->row()->row->x_height() + it_->row()->row->ascenders() - |
569 | 0 | it_->row()->row->descenders(); |
570 | 0 | *descenders = it_->row()->row->descenders(); |
571 | 0 | *ascenders = it_->row()->row->ascenders(); |
572 | 0 | } |
573 | | |
574 | | void PageIterator::Orientation(tesseract::Orientation *orientation, |
575 | | tesseract::WritingDirection *writing_direction, |
576 | | tesseract::TextlineOrder *textline_order, |
577 | 0 | float *deskew_angle) const { |
578 | 0 | auto *block_res = it_->block(); |
579 | 0 | if (block_res == nullptr) { |
580 | | // Nothing can be done, so return default values. |
581 | 0 | *orientation = ORIENTATION_PAGE_UP; |
582 | 0 | *writing_direction = WRITING_DIRECTION_LEFT_TO_RIGHT; |
583 | 0 | *textline_order = TEXTLINE_ORDER_TOP_TO_BOTTOM; |
584 | 0 | return; |
585 | 0 | } |
586 | 0 | auto *block = block_res->block; |
587 | | |
588 | | // Orientation |
589 | 0 | FCOORD up_in_image(0.0, 1.0); |
590 | 0 | up_in_image.unrotate(block->classify_rotation()); |
591 | 0 | up_in_image.rotate(block->re_rotation()); |
592 | |
|
593 | 0 | if (up_in_image.x() == 0.0F) { |
594 | 0 | if (up_in_image.y() > 0.0F) { |
595 | 0 | *orientation = ORIENTATION_PAGE_UP; |
596 | 0 | } else { |
597 | 0 | *orientation = ORIENTATION_PAGE_DOWN; |
598 | 0 | } |
599 | 0 | } else if (up_in_image.x() > 0.0F) { |
600 | 0 | *orientation = ORIENTATION_PAGE_RIGHT; |
601 | 0 | } else { |
602 | 0 | *orientation = ORIENTATION_PAGE_LEFT; |
603 | 0 | } |
604 | | |
605 | | // Writing direction |
606 | 0 | bool is_vertical_text = (block->classify_rotation().x() == 0.0); |
607 | 0 | bool right_to_left = block->right_to_left(); |
608 | 0 | *writing_direction = is_vertical_text |
609 | 0 | ? WRITING_DIRECTION_TOP_TO_BOTTOM |
610 | 0 | : (right_to_left ? WRITING_DIRECTION_RIGHT_TO_LEFT |
611 | 0 | : WRITING_DIRECTION_LEFT_TO_RIGHT); |
612 | | |
613 | | // Textline Order |
614 | 0 | const bool is_mongolian = false; // TODO(eger): fix me |
615 | 0 | *textline_order = is_vertical_text |
616 | 0 | ? (is_mongolian ? TEXTLINE_ORDER_LEFT_TO_RIGHT |
617 | 0 | : TEXTLINE_ORDER_RIGHT_TO_LEFT) |
618 | 0 | : TEXTLINE_ORDER_TOP_TO_BOTTOM; |
619 | | |
620 | | // Deskew angle |
621 | 0 | FCOORD skew = block->skew(); // true horizontal for textlines |
622 | 0 | *deskew_angle = -skew.angle(); |
623 | 0 | } |
624 | | |
625 | | void PageIterator::ParagraphInfo(tesseract::ParagraphJustification *just, |
626 | | bool *is_list_item, bool *is_crown, |
627 | 0 | int *first_line_indent) const { |
628 | 0 | *just = tesseract::JUSTIFICATION_UNKNOWN; |
629 | 0 | if (!it_->row() || !it_->row()->row || !it_->row()->row->para() || |
630 | 0 | !it_->row()->row->para()->model) { |
631 | 0 | return; |
632 | 0 | } |
633 | | |
634 | 0 | PARA *para = it_->row()->row->para(); |
635 | 0 | *is_list_item = para->is_list_item; |
636 | 0 | *is_crown = para->is_very_first_or_continuation; |
637 | 0 | *first_line_indent = para->model->first_indent() - para->model->body_indent(); |
638 | 0 | *just = para->model->justification(); |
639 | 0 | } |
640 | | |
641 | | /** |
642 | | * Sets up the internal data for iterating the blobs of a new word, then |
643 | | * moves the iterator to the given offset. |
644 | | */ |
645 | 11.9M | void PageIterator::BeginWord(int offset) { |
646 | 11.9M | WERD_RES *word_res = it_->word(); |
647 | 11.9M | if (word_res == nullptr) { |
648 | | // This is a non-text block, so there is no word. |
649 | 399k | word_length_ = 0; |
650 | 399k | blob_index_ = 0; |
651 | 399k | word_ = nullptr; |
652 | 399k | return; |
653 | 399k | } |
654 | 11.5M | if (word_res->best_choice != nullptr) { |
655 | | // Recognition has been done, so we are using the box_word, which |
656 | | // is already baseline denormalized. |
657 | 11.5M | word_length_ = word_res->best_choice->length(); |
658 | 11.5M | if (word_res->box_word != nullptr) { |
659 | 11.5M | if (word_res->box_word->length() != static_cast<unsigned>(word_length_)) { |
660 | 0 | tprintf("Corrupted word! best_choice[len=%d] = %s, box_word[len=%d]: ", |
661 | 0 | word_length_, word_res->best_choice->unichar_string().c_str(), |
662 | 0 | word_res->box_word->length()); |
663 | 0 | word_res->box_word->bounding_box().print(); |
664 | 0 | } |
665 | 11.5M | ASSERT_HOST(word_res->box_word->length() == |
666 | 11.5M | static_cast<unsigned>(word_length_)); |
667 | 11.5M | } |
668 | 11.5M | word_ = nullptr; |
669 | | // We will be iterating the box_word. |
670 | 11.5M | delete cblob_it_; |
671 | 11.5M | cblob_it_ = nullptr; |
672 | 11.5M | } else { |
673 | | // No recognition yet, so a "symbol" is a cblob. |
674 | 0 | word_ = word_res->word; |
675 | 0 | ASSERT_HOST(word_->cblob_list() != nullptr); |
676 | 0 | word_length_ = word_->cblob_list()->length(); |
677 | 0 | if (cblob_it_ == nullptr) { |
678 | 0 | cblob_it_ = new C_BLOB_IT; |
679 | 0 | } |
680 | 0 | cblob_it_->set_to_list(word_->cblob_list()); |
681 | 0 | } |
682 | 11.5M | for (blob_index_ = 0; blob_index_ < offset; ++blob_index_) { |
683 | 0 | if (cblob_it_ != nullptr) { |
684 | 0 | cblob_it_->forward(); |
685 | 0 | } |
686 | 0 | } |
687 | 11.5M | } |
688 | | |
689 | 0 | bool PageIterator::SetWordBlamerBundle(BlamerBundle *blamer_bundle) { |
690 | 0 | if (it_->word() != nullptr) { |
691 | 0 | it_->word()->blamer_bundle = blamer_bundle; |
692 | 0 | return true; |
693 | 0 | } else { |
694 | 0 | return false; |
695 | 0 | } |
696 | 0 | } |
697 | | |
698 | | } // namespace tesseract. |