tesseract 4.1.1
Loading...
Searching...
No Matches
blobs.cpp
Go to the documentation of this file.
1/* -*-C-*-
2 ********************************************************************************
3 *
4 * File: blobs.cpp (Formerly blobs.c)
5 * Description: Blob definition
6 * Author: Mark Seaman, OCR Technology
7 *
8 * (c) Copyright 1989, Hewlett-Packard Company.
9 ** Licensed under the Apache License, Version 2.0 (the "License");
10 ** you may not use this file except in compliance with the License.
11 ** You may obtain a copy of the License at
12 ** http://www.apache.org/licenses/LICENSE-2.0
13 ** Unless required by applicable law or agreed to in writing, software
14 ** distributed under the License is distributed on an "AS IS" BASIS,
15 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 ** See the License for the specific language governing permissions and
17 ** limitations under the License.
18 *
19 *********************************************************************************/
20
21/*----------------------------------------------------------------------
22 I n c l u d e s
23----------------------------------------------------------------------*/
24// Include automatically generated configuration file if running autoconf.
25#ifdef HAVE_CONFIG_H
26#include "config_auto.h"
27#endif
28
29#include "blobs.h"
30#include "ccstruct.h"
31#include "clst.h"
32#include "helpers.h"
33#include "linlsq.h"
34#include "normalis.h"
35#include "ocrblock.h"
36#include "ocrrow.h"
37#include "points.h"
38#include "polyaprx.h"
39#include "werd.h"
40
41#include <algorithm>
42
44
45// A Vector representing the "vertical" direction when measuring the
46// divisiblity of blobs into multiple blobs just by separating outlines.
47// See divisible_blob below for the use.
49// A vector representing the "vertical" direction for italic text for use
50// when separating outlines. Using it actually deteriorates final accuracy,
51// so it is only used for ApplyBoxes chopping to get a better segmentation.
53
54/*----------------------------------------------------------------------
55 F u n c t i o n s
56----------------------------------------------------------------------*/
57
59
60// Returns true when the two line segments cross each other.
61// (Moved from outlines.cpp).
62// Finds where the projected lines would cross and then checks to see if the
63// point of intersection lies on both of the line segments. If it does
64// then these two segments cross.
65/* static */
66bool TPOINT::IsCrossed(const TPOINT& a0, const TPOINT& a1, const TPOINT& b0,
67 const TPOINT& b1) {
68 TPOINT b0a1, b0a0, a1b1, b0b1, a1a0;
69
70 b0a1.x = a1.x - b0.x;
71 b0a0.x = a0.x - b0.x;
72 a1b1.x = b1.x - a1.x;
73 b0b1.x = b1.x - b0.x;
74 a1a0.x = a0.x - a1.x;
75 b0a1.y = a1.y - b0.y;
76 b0a0.y = a0.y - b0.y;
77 a1b1.y = b1.y - a1.y;
78 b0b1.y = b1.y - b0.y;
79 a1a0.y = a0.y - a1.y;
80
81 int b0a1xb0b1 = b0a1.cross(b0b1);
82 int b0b1xb0a0 = b0b1.cross(b0a0);
83 int a1b1xa1a0 = a1b1.cross(a1a0);
84 // For clarity, we want a1a0.cross(a1b0) here but we have b0a1 instead of a1b0
85 // so use -a1b0.cross(b0a1) instead, which is the same.
86 int a1a0xa1b0 = -a1a0.cross(b0a1);
87
88 return ((b0a1xb0b1 > 0 && b0b1xb0a0 > 0) ||
89 (b0a1xb0b1 < 0 && b0b1xb0a0 < 0)) &&
90 ((a1b1xa1a0 > 0 && a1a0xa1b0 > 0) || (a1b1xa1a0 < 0 && a1a0xa1b0 < 0));
91}
92
93// Consume the circular list of EDGEPTs to make a TESSLINE.
95 auto* result = new TESSLINE;
96 result->loop = outline;
97 if (outline->src_outline != nullptr) {
98 // ASSUMPTION: This function is only ever called from ApproximateOutline
99 // and therefore either all points have a src_outline or all do not.
100 // Just as SetupFromPos sets the vectors from the vertices, setup the
101 // step_count members to indicate the (positive) number of original
102 // C_OUTLINE steps to the next vertex.
103 EDGEPT* pt = outline;
104 do {
105 pt->step_count = pt->next->start_step - pt->start_step;
106 if (pt->step_count < 0) pt->step_count += pt->src_outline->pathlength();
107 pt = pt->next;
108 } while (pt != outline);
109 }
110 result->SetupFromPos();
111 return result;
112}
113
114// Copies the data and the outline, but leaves next untouched.
115void TESSLINE::CopyFrom(const TESSLINE& src) {
116 Clear();
117 topleft = src.topleft;
118 botright = src.botright;
119 start = src.start;
120 is_hole = src.is_hole;
121 if (src.loop != nullptr) {
122 EDGEPT* prevpt = nullptr;
123 EDGEPT* newpt = nullptr;
124 EDGEPT* srcpt = src.loop;
125 do {
126 newpt = new EDGEPT(*srcpt);
127 if (prevpt == nullptr) {
128 loop = newpt;
129 } else {
130 newpt->prev = prevpt;
131 prevpt->next = newpt;
132 }
133 prevpt = newpt;
134 srcpt = srcpt->next;
135 } while (srcpt != src.loop);
136 loop->prev = newpt;
137 newpt->next = loop;
138 }
139}
140
141// Deletes owned data.
143 if (loop == nullptr) return;
144
145 EDGEPT* this_edge = loop;
146 do {
147 EDGEPT* next_edge = this_edge->next;
148 delete this_edge;
149 this_edge = next_edge;
150 } while (this_edge != loop);
151 loop = nullptr;
152}
153
154// Normalize in-place using the DENORM.
155void TESSLINE::Normalize(const DENORM& denorm) {
156 EDGEPT* pt = loop;
157 do {
158 denorm.LocalNormTransform(pt->pos, &pt->pos);
159 pt = pt->next;
160 } while (pt != loop);
161 SetupFromPos();
162}
163
164// Rotates by the given rotation in place.
165void TESSLINE::Rotate(const FCOORD rot) {
166 EDGEPT* pt = loop;
167 do {
168 int tmp = static_cast<int>(
169 floor(pt->pos.x * rot.x() - pt->pos.y * rot.y() + 0.5));
170 pt->pos.y = static_cast<int>(
171 floor(pt->pos.y * rot.x() + pt->pos.x * rot.y() + 0.5));
172 pt->pos.x = tmp;
173 pt = pt->next;
174 } while (pt != loop);
175 SetupFromPos();
176}
177
178// Moves by the given vec in place.
179void TESSLINE::Move(const ICOORD vec) {
180 EDGEPT* pt = loop;
181 do {
182 pt->pos.x += vec.x();
183 pt->pos.y += vec.y();
184 pt = pt->next;
185 } while (pt != loop);
186 SetupFromPos();
187}
188
189// Scales by the given factor in place.
190void TESSLINE::Scale(float factor) {
191 EDGEPT* pt = loop;
192 do {
193 pt->pos.x = static_cast<int>(floor(pt->pos.x * factor + 0.5));
194 pt->pos.y = static_cast<int>(floor(pt->pos.y * factor + 0.5));
195 pt = pt->next;
196 } while (pt != loop);
197 SetupFromPos();
198}
199
200// Sets up the start and vec members of the loop from the pos members.
202 EDGEPT* pt = loop;
203 do {
204 pt->vec.x = pt->next->pos.x - pt->pos.x;
205 pt->vec.y = pt->next->pos.y - pt->pos.y;
206 pt = pt->next;
207 } while (pt != loop);
208 start = pt->pos;
210}
211
212// Recomputes the bounding box from the points in the loop.
214 int minx = INT32_MAX;
215 int miny = INT32_MAX;
216 int maxx = -INT32_MAX;
217 int maxy = -INT32_MAX;
218
219 // Find boundaries.
220 start = loop->pos;
221 EDGEPT* this_edge = loop;
222 do {
223 if (!this_edge->IsHidden() || !this_edge->prev->IsHidden()) {
224 if (this_edge->pos.x < minx) minx = this_edge->pos.x;
225 if (this_edge->pos.y < miny) miny = this_edge->pos.y;
226 if (this_edge->pos.x > maxx) maxx = this_edge->pos.x;
227 if (this_edge->pos.y > maxy) maxy = this_edge->pos.y;
228 }
229 this_edge = this_edge->next;
230 } while (this_edge != loop);
231 // Reset bounds.
232 topleft.x = minx;
233 topleft.y = maxy;
234 botright.x = maxx;
235 botright.y = miny;
236}
237
238// Computes the min and max cross product of the outline points with the
239// given vec and returns the results in min_xp and max_xp. Geometrically
240// this is the left and right edge of the outline perpendicular to the
241// given direction, but to get the distance units correct, you would
242// have to divide by the modulus of vec.
243void TESSLINE::MinMaxCrossProduct(const TPOINT vec, int* min_xp,
244 int* max_xp) const {
245 *min_xp = INT32_MAX;
246 *max_xp = INT32_MIN;
247 EDGEPT* this_edge = loop;
248 do {
249 if (!this_edge->IsHidden() || !this_edge->prev->IsHidden()) {
250 int product = this_edge->pos.cross(vec);
251 UpdateRange(product, min_xp, max_xp);
252 }
253 this_edge = this_edge->next;
254 } while (this_edge != loop);
255}
256
259}
260
261#ifndef GRAPHICS_DISABLED
263 ScrollView::Color child_color) {
264 if (is_hole)
265 window->Pen(child_color);
266 else
267 window->Pen(color);
268 window->SetCursor(start.x, start.y);
269 EDGEPT* pt = loop;
270 do {
271 bool prev_hidden = pt->IsHidden();
272 pt = pt->next;
273 if (prev_hidden)
274 window->SetCursor(pt->pos.x, pt->pos.y);
275 else
276 window->DrawTo(pt->pos.x, pt->pos.y);
277 } while (pt != loop);
278}
279#endif // GRAPHICS_DISABLED
280
281// Returns the first non-hidden EDGEPT that has a different src_outline to
282// its predecessor, or, if all the same, the lowest indexed point.
284 EDGEPT* best_start = loop;
285 int best_step = loop->start_step;
286 // Iterate the polygon.
287 EDGEPT* pt = loop;
288 do {
289 if (pt->IsHidden()) continue;
290 if (pt->prev->IsHidden() || pt->prev->src_outline != pt->src_outline)
291 return pt; // Qualifies as the best.
292 if (pt->start_step < best_step) {
293 best_step = pt->start_step;
294 best_start = pt;
295 }
296 } while ((pt = pt->next) != loop);
297 return best_start;
298}
299
300// Iterate the given list of outlines, converting to TESSLINE by polygonal
301// approximation and recursively any children, returning the current tail
302// of the resulting list of TESSLINEs.
303static TESSLINE** ApproximateOutlineList(bool allow_detailed_fx,
304 C_OUTLINE_LIST* outlines,
305 bool children, TESSLINE** tail) {
306 C_OUTLINE_IT ol_it(outlines);
307 for (ol_it.mark_cycle_pt(); !ol_it.cycled_list(); ol_it.forward()) {
308 C_OUTLINE* outline = ol_it.data();
309 if (outline->pathlength() > 0) {
310 TESSLINE* tessline = ApproximateOutline(allow_detailed_fx, outline);
311 tessline->is_hole = children;
312 *tail = tessline;
313 tail = &tessline->next;
314 }
315 if (!outline->child()->empty()) {
316 tail = ApproximateOutlineList(allow_detailed_fx, outline->child(), true,
317 tail);
318 }
319 }
320 return tail;
321}
322
323// Factory to build a TBLOB from a C_BLOB with polygonal approximation along
324// the way. If allow_detailed_fx is true, the EDGEPTs in the returned TBLOB
325// contain pointers to the input C_OUTLINEs that enable higher-resolution
326// feature extraction that does not use the polygonal approximation.
327TBLOB* TBLOB::PolygonalCopy(bool allow_detailed_fx, C_BLOB* src) {
328 auto* tblob = new TBLOB;
329 ApproximateOutlineList(allow_detailed_fx, src->out_list(), false,
330 &tblob->outlines);
331 return tblob;
332}
333
334// Factory builds a blob with no outlines, but copies the other member data.
336 auto* blob = new TBLOB;
337 blob->denorm_ = src.denorm_;
338 return blob;
339}
340
341// Normalizes the blob for classification only if needed.
342// (Normally this means a non-zero classify rotation.)
343// If no Normalization is needed, then nullptr is returned, and the input blob
344// can be used directly. Otherwise a new TBLOB is returned which must be
345// deleted after use.
347 TBLOB* rotated_blob = nullptr;
348 // If necessary, copy the blob and rotate it. The rotation is always
349 // +/- 90 degrees, as 180 was already taken care of.
350 if (denorm_.block() != nullptr &&
351 denorm_.block()->classify_rotation().y() != 0.0) {
352 TBOX box = bounding_box();
353 int x_middle = (box.left() + box.right()) / 2;
354 int y_middle = (box.top() + box.bottom()) / 2;
355 rotated_blob = new TBLOB(*this);
356 const FCOORD& rotation = denorm_.block()->classify_rotation();
357 // Move the rotated blob back to the same y-position so that we
358 // can still distinguish similar glyphs with differeny y-position.
359 float target_y =
361 (rotation.y() > 0 ? x_middle - box.left() : box.right() - x_middle);
362 rotated_blob->Normalize(nullptr, &rotation, &denorm_, x_middle, y_middle,
363 1.0f, 1.0f, 0.0f, target_y, denorm_.inverse(),
364 denorm_.pix());
365 }
366 return rotated_blob;
367}
368
369// Copies the data and the outline, but leaves next untouched.
370void TBLOB::CopyFrom(const TBLOB& src) {
371 Clear();
372 TESSLINE* prev_outline = nullptr;
373 for (TESSLINE* srcline = src.outlines; srcline != nullptr;
374 srcline = srcline->next) {
375 auto* new_outline = new TESSLINE(*srcline);
376 if (outlines == nullptr)
377 outlines = new_outline;
378 else
379 prev_outline->next = new_outline;
380 prev_outline = new_outline;
381 }
382 denorm_ = src.denorm_;
383}
384
385// Deletes owned data.
387 for (TESSLINE* next_outline = nullptr; outlines != nullptr;
388 outlines = next_outline) {
389 next_outline = outlines->next;
390 delete outlines;
391 }
392}
393
394// Sets up the built-in DENORM and normalizes the blob in-place.
395// For parameters see DENORM::SetupNormalization, plus the inverse flag for
396// this blob and the Pix for the full image.
397void TBLOB::Normalize(const BLOCK* block, const FCOORD* rotation,
398 const DENORM* predecessor, float x_origin, float y_origin,
399 float x_scale, float y_scale, float final_xshift,
400 float final_yshift, bool inverse, Pix* pix) {
401 denorm_.SetupNormalization(block, rotation, predecessor, x_origin, y_origin,
402 x_scale, y_scale, final_xshift, final_yshift);
403 denorm_.set_inverse(inverse);
404 denorm_.set_pix(pix);
405 // TODO(rays) outline->Normalize is more accurate, but breaks tests due
406 // the changes it makes. Reinstate this code with a retraining.
407 // The reason this change is troublesome is that it normalizes for the
408 // baseline value computed independently at each x-coord. If the baseline
409 // is not horizontal, this introduces shear into the normalized blob, which
410 // is useful on the rare occasions that the baseline is really curved, but
411 // the baselines need to be stabilized the rest of the time.
412#if 0
413 for (TESSLINE* outline = outlines; outline != nullptr; outline = outline->next) {
414 outline->Normalize(denorm_);
415 }
416#else
417 denorm_.LocalNormBlob(this);
418#endif
419}
420
421// Rotates by the given rotation in place.
422void TBLOB::Rotate(const FCOORD rotation) {
423 for (TESSLINE* outline = outlines; outline != nullptr;
424 outline = outline->next) {
425 outline->Rotate(rotation);
426 }
427}
428
429// Moves by the given vec in place.
430void TBLOB::Move(const ICOORD vec) {
431 for (TESSLINE* outline = outlines; outline != nullptr;
432 outline = outline->next) {
433 outline->Move(vec);
434 }
435}
436
437// Scales by the given factor in place.
438void TBLOB::Scale(float factor) {
439 for (TESSLINE* outline = outlines; outline != nullptr;
440 outline = outline->next) {
441 outline->Scale(factor);
442 }
443}
444
445// Recomputes the bounding boxes of the outlines.
447 for (TESSLINE* outline = outlines; outline != nullptr;
448 outline = outline->next) {
449 outline->ComputeBoundingBox();
450 }
451}
452
453// Returns the number of outlines.
455 int result = 0;
456 for (TESSLINE* outline = outlines; outline != nullptr;
457 outline = outline->next)
458 ++result;
459 return result;
460}
461
462/**********************************************************************
463 * TBLOB::bounding_box()
464 *
465 * Compute the bounding_box of a compound blob, defined to be the
466 * bounding box of the union of all top-level outlines in the blob.
467 **********************************************************************/
469 if (outlines == nullptr) return TBOX(0, 0, 0, 0);
470 TESSLINE* outline = outlines;
471 TBOX box = outline->bounding_box();
472 for (outline = outline->next; outline != nullptr; outline = outline->next) {
473 box += outline->bounding_box();
474 }
475 return box;
476}
477
478// Finds and deletes any duplicate outlines in this blob, without deleting
479// their EDGEPTs.
481 for (TESSLINE* outline = outlines; outline != nullptr;
482 outline = outline->next) {
483 TESSLINE* last_outline = outline;
484 for (TESSLINE* other_outline = outline->next; other_outline != nullptr;
485 last_outline = other_outline, other_outline = other_outline->next) {
486 if (outline->SameBox(*other_outline)) {
487 last_outline->next = other_outline->next;
488 // This doesn't leak - the outlines share the EDGEPTs.
489 other_outline->loop = nullptr;
490 delete other_outline;
491 other_outline = last_outline;
492 // If it is part of a cut, then it can't be a hole any more.
493 outline->is_hole = false;
494 }
495 }
496 }
497}
498
499// Swaps the outlines of *this and next if needed to keep the centers in
500// increasing x.
502 TBOX box = bounding_box();
503 TBOX next_box = next->bounding_box();
504 if (box.x_middle() > next_box.x_middle()) {
505 Swap(&outlines, &next->outlines);
506 }
507}
508
509#ifndef GRAPHICS_DISABLED
511 ScrollView::Color child_color) {
512 for (TESSLINE* outline = outlines; outline != nullptr;
513 outline = outline->next)
514 outline->plot(window, color, child_color);
515}
516#endif // GRAPHICS_DISABLED
517
518// Computes the center of mass and second moments for the old baseline and
519// 2nd moment normalizations. Returns the outline length.
520// The input denorm should be the normalizations that have been applied from
521// the image to the current state of this TBLOB.
522int TBLOB::ComputeMoments(FCOORD* center, FCOORD* second_moments) const {
523 // Compute 1st and 2nd moments of the original outline.
524 LLSQ accumulator;
525 TBOX box = bounding_box();
526 // Iterate the outlines, accumulating edges relative the box.botleft().
527 CollectEdges(box, nullptr, &accumulator, nullptr, nullptr);
528 *center = accumulator.mean_point() + box.botleft();
529 // The 2nd moments are just the standard deviation of the point positions.
530 double x2nd = sqrt(accumulator.x_variance());
531 double y2nd = sqrt(accumulator.y_variance());
532 if (x2nd < 1.0) x2nd = 1.0;
533 if (y2nd < 1.0) y2nd = 1.0;
534 second_moments->set_x(x2nd);
535 second_moments->set_y(y2nd);
536 return accumulator.count();
537}
538
539// Computes the precise bounding box of the coords that are generated by
540// GetEdgeCoords. This may be different from the bounding box of the polygon.
541void TBLOB::GetPreciseBoundingBox(TBOX* precise_box) const {
542 TBOX box = bounding_box();
543 *precise_box = TBOX();
544 CollectEdges(box, precise_box, nullptr, nullptr, nullptr);
545 precise_box->move(box.botleft());
546}
547
548// Adds edges to the given vectors.
549// For all the edge steps in all the outlines, or polygonal approximation
550// where there are no edge steps, collects the steps into x_coords/y_coords.
551// x_coords is a collection of the x-coords of vertical edges for each
552// y-coord starting at box.bottom().
553// y_coords is a collection of the y-coords of horizontal edges for each
554// x-coord starting at box.left().
555// Eg x_coords[0] is a collection of the x-coords of edges at y=bottom.
556// Eg x_coords[1] is a collection of the x-coords of edges at y=bottom + 1.
559 GenericVector<GenericVector<int> >* y_coords) const {
560 GenericVector<int> empty;
561 x_coords->init_to_size(box.height(), empty);
562 y_coords->init_to_size(box.width(), empty);
563 CollectEdges(box, nullptr, nullptr, x_coords, y_coords);
564 // Sort the output vectors.
565 for (int i = 0; i < x_coords->size(); ++i) (*x_coords)[i].sort();
566 for (int i = 0; i < y_coords->size(); ++i) (*y_coords)[i].sort();
567}
568
569// Accumulates the segment between pt1 and pt2 in the LLSQ, quantizing over
570// the integer coordinate grid to properly weight long vectors.
571static void SegmentLLSQ(const FCOORD& pt1, const FCOORD& pt2,
572 LLSQ* accumulator) {
573 FCOORD step(pt2);
574 step -= pt1;
575 int xstart = IntCastRounded(std::min(pt1.x(), pt2.x()));
576 int xend = IntCastRounded(std::max(pt1.x(), pt2.x()));
577 int ystart = IntCastRounded(std::min(pt1.y(), pt2.y()));
578 int yend = IntCastRounded(std::max(pt1.y(), pt2.y()));
579 if (xstart == xend && ystart == yend) return; // Nothing to do.
580 double weight = step.length() / (xend - xstart + yend - ystart);
581 // Compute and save the y-position at the middle of each x-step.
582 for (int x = xstart; x < xend; ++x) {
583 double y = pt1.y() + step.y() * (x + 0.5 - pt1.x()) / step.x();
584 accumulator->add(x + 0.5, y, weight);
585 }
586 // Compute and save the x-position at the middle of each y-step.
587 for (int y = ystart; y < yend; ++y) {
588 double x = pt1.x() + step.x() * (y + 0.5 - pt1.y()) / step.y();
589 accumulator->add(x, y + 0.5, weight);
590 }
591}
592
593// Adds any edges from a single segment of outline between pt1 and pt2 to
594// the x_coords, y_coords vectors. pt1 and pt2 should be relative to the
595// bottom-left of the bounding box, hence indices to x_coords, y_coords
596// are clipped to ([0,x_limit], [0,y_limit]).
597// See GetEdgeCoords above for a description of x_coords, y_coords.
598static void SegmentCoords(const FCOORD& pt1, const FCOORD& pt2, int x_limit,
599 int y_limit,
601 GenericVector<GenericVector<int> >* y_coords) {
602 FCOORD step(pt2);
603 step -= pt1;
604 int start =
605 ClipToRange(IntCastRounded(std::min(pt1.x(), pt2.x())), 0, x_limit);
606 int end = ClipToRange(IntCastRounded(std::max(pt1.x(), pt2.x())), 0, x_limit);
607 for (int x = start; x < end; ++x) {
608 int y = IntCastRounded(pt1.y() + step.y() * (x + 0.5 - pt1.x()) / step.x());
609 (*y_coords)[x].push_back(y);
610 }
611 start = ClipToRange(IntCastRounded(std::min(pt1.y(), pt2.y())), 0, y_limit);
612 end = ClipToRange(IntCastRounded(std::max(pt1.y(), pt2.y())), 0, y_limit);
613 for (int y = start; y < end; ++y) {
614 int x = IntCastRounded(pt1.x() + step.x() * (y + 0.5 - pt1.y()) / step.y());
615 (*x_coords)[y].push_back(x);
616 }
617}
618
619// Adds any edges from a single segment of outline between pt1 and pt2 to
620// the bbox such that it guarantees to contain anything produced by
621// SegmentCoords.
622static void SegmentBBox(const FCOORD& pt1, const FCOORD& pt2, TBOX* bbox) {
623 FCOORD step(pt2);
624 step -= pt1;
625 int x1 = IntCastRounded(std::min(pt1.x(), pt2.x()));
626 int x2 = IntCastRounded(std::max(pt1.x(), pt2.x()));
627 if (x2 > x1) {
628 int y1 =
629 IntCastRounded(pt1.y() + step.y() * (x1 + 0.5 - pt1.x()) / step.x());
630 int y2 =
631 IntCastRounded(pt1.y() + step.y() * (x2 - 0.5 - pt1.x()) / step.x());
632 TBOX point(x1, std::min(y1, y2), x2, std::max(y1, y2));
633 *bbox += point;
634 }
635 int y1 = IntCastRounded(std::min(pt1.y(), pt2.y()));
636 int y2 = IntCastRounded(std::max(pt1.y(), pt2.y()));
637 if (y2 > y1) {
638 int x1 =
639 IntCastRounded(pt1.x() + step.x() * (y1 + 0.5 - pt1.y()) / step.y());
640 int x2 =
641 IntCastRounded(pt1.x() + step.x() * (y2 - 0.5 - pt1.y()) / step.y());
642 TBOX point(std::min(x1, x2), y1, std::max(x1, x2), y2);
643 *bbox += point;
644 }
645}
646
647// Collects edges into the given bounding box, LLSQ accumulator and/or x_coords,
648// y_coords vectors.
649// For a description of x_coords/y_coords, see GetEdgeCoords above.
650// Startpt to lastpt, inclusive, MUST have the same src_outline member,
651// which may be nullptr. The vector from lastpt to its next is included in
652// the accumulation. Hidden edges should be excluded by the caller.
653// The input denorm should be the normalizations that have been applied from
654// the image to the current state of the TBLOB from which startpt, lastpt come.
655// box is the bounding box of the blob from which the EDGEPTs are taken and
656// indices into x_coords, y_coords are offset by box.botleft().
657static void CollectEdgesOfRun(const EDGEPT* startpt, const EDGEPT* lastpt,
658 const DENORM& denorm, const TBOX& box,
659 TBOX* bounding_box, LLSQ* accumulator,
661 GenericVector<GenericVector<int> >* y_coords) {
662 const C_OUTLINE* outline = startpt->src_outline;
663 int x_limit = box.width() - 1;
664 int y_limit = box.height() - 1;
665 if (outline != nullptr) {
666 // Use higher-resolution edge points stored on the outline.
667 // The outline coordinates may not match the binary image because of the
668 // rotation for vertical text lines, but the root_denorm IS the matching
669 // start of the DENORM chain.
670 const DENORM* root_denorm = denorm.RootDenorm();
671 int step_length = outline->pathlength();
672 int start_index = startpt->start_step;
673 // Note that if this run straddles the wrap-around point of the outline,
674 // that lastpt->start_step may have a lower index than startpt->start_step,
675 // and we want to use an end_index that allows us to use a positive
676 // increment, so we add step_length if necessary, but that may be beyond the
677 // bounds of the outline steps/ due to wrap-around, so we use % step_length
678 // everywhere, except for start_index.
679 int end_index = lastpt->start_step + lastpt->step_count;
680 if (end_index <= start_index) end_index += step_length;
681 // pos is the integer coordinates of the binary image steps.
682 ICOORD pos = outline->position_at_index(start_index);
683 FCOORD origin(box.left(), box.bottom());
684 // f_pos is a floating-point version of pos that offers improved edge
685 // positioning using greyscale information or smoothing of edge steps.
686 FCOORD f_pos = outline->sub_pixel_pos_at_index(pos, start_index);
687 // pos_normed is f_pos after the appropriate normalization, and relative
688 // to origin.
689 // prev_normed is the previous value of pos_normed.
690 FCOORD prev_normed;
691 denorm.NormTransform(root_denorm, f_pos, &prev_normed);
692 prev_normed -= origin;
693 for (int index = start_index; index < end_index; ++index) {
694 ICOORD step = outline->step(index % step_length);
695 // Only use the point if its edge strength is positive. This excludes
696 // points that don't provide useful information, eg
697 // ___________
698 // |___________
699 // The vertical step provides only noisy, damaging information, as even
700 // with a greyscale image, the positioning of the edge there may be a
701 // fictitious extrapolation, so previous processing has eliminated it.
702 if (outline->edge_strength_at_index(index % step_length) > 0) {
703 FCOORD f_pos =
704 outline->sub_pixel_pos_at_index(pos, index % step_length);
705 FCOORD pos_normed;
706 denorm.NormTransform(root_denorm, f_pos, &pos_normed);
707 pos_normed -= origin;
708 // Accumulate the information that is selected by the caller.
709 if (bounding_box != nullptr) {
710 SegmentBBox(pos_normed, prev_normed, bounding_box);
711 }
712 if (accumulator != nullptr) {
713 SegmentLLSQ(pos_normed, prev_normed, accumulator);
714 }
715 if (x_coords != nullptr && y_coords != nullptr) {
716 SegmentCoords(pos_normed, prev_normed, x_limit, y_limit, x_coords,
717 y_coords);
718 }
719 prev_normed = pos_normed;
720 }
721 pos += step;
722 }
723 } else {
724 // There is no outline, so we are forced to use the polygonal approximation.
725 const EDGEPT* endpt = lastpt->next;
726 const EDGEPT* pt = startpt;
727 do {
728 FCOORD next_pos(pt->next->pos.x - box.left(),
729 pt->next->pos.y - box.bottom());
730 FCOORD pos(pt->pos.x - box.left(), pt->pos.y - box.bottom());
731 if (bounding_box != nullptr) {
732 SegmentBBox(next_pos, pos, bounding_box);
733 }
734 if (accumulator != nullptr) {
735 SegmentLLSQ(next_pos, pos, accumulator);
736 }
737 if (x_coords != nullptr && y_coords != nullptr) {
738 SegmentCoords(next_pos, pos, x_limit, y_limit, x_coords, y_coords);
739 }
740 } while ((pt = pt->next) != endpt);
741 }
742}
743
744// For all the edge steps in all the outlines, or polygonal approximation
745// where there are no edge steps, collects the steps into the bounding_box,
746// llsq and/or the x_coords/y_coords. Both are used in different kinds of
747// normalization.
748// For a description of x_coords, y_coords, see GetEdgeCoords above.
749void TBLOB::CollectEdges(const TBOX& box, TBOX* bounding_box, LLSQ* llsq,
751 GenericVector<GenericVector<int> >* y_coords) const {
752 // Iterate the outlines.
753 for (const TESSLINE* ol = outlines; ol != nullptr; ol = ol->next) {
754 // Iterate the polygon.
755 EDGEPT* loop_pt = ol->FindBestStartPt();
756 EDGEPT* pt = loop_pt;
757 if (pt == nullptr) continue;
758 do {
759 if (pt->IsHidden()) continue;
760 // Find a run of equal src_outline.
761 EDGEPT* last_pt = pt;
762 do {
763 last_pt = last_pt->next;
764 } while (last_pt != loop_pt && !last_pt->IsHidden() &&
765 last_pt->src_outline == pt->src_outline);
766 last_pt = last_pt->prev;
767 CollectEdgesOfRun(pt, last_pt, denorm_, box, bounding_box, llsq, x_coords,
768 y_coords);
769 pt = last_pt;
770 } while ((pt = pt->next) != loop_pt);
771 }
772}
773
774// Factory to build a TWERD from a (C_BLOB) WERD, with polygonal
775// approximation along the way.
776TWERD* TWERD::PolygonalCopy(bool allow_detailed_fx, WERD* src) {
777 auto* tessword = new TWERD;
778 tessword->latin_script = src->flag(W_SCRIPT_IS_LATIN);
779 C_BLOB_IT b_it(src->cblob_list());
780 for (b_it.mark_cycle_pt(); !b_it.cycled_list(); b_it.forward()) {
781 C_BLOB* blob = b_it.data();
782 TBLOB* tblob = TBLOB::PolygonalCopy(allow_detailed_fx, blob);
783 tessword->blobs.push_back(tblob);
784 }
785 return tessword;
786}
787
788// Baseline normalizes the blobs in-place, recording the normalization in the
789// DENORMs in the blobs.
790void TWERD::BLNormalize(const BLOCK* block, const ROW* row, Pix* pix,
791 bool inverse, float x_height, float baseline_shift,
792 bool numeric_mode, tesseract::OcrEngineMode hint,
793 const TBOX* norm_box, DENORM* word_denorm) {
794 TBOX word_box = bounding_box();
795 if (norm_box != nullptr) word_box = *norm_box;
796 float word_middle = (word_box.left() + word_box.right()) / 2.0f;
797 float input_y_offset = 0.0f;
798 auto final_y_offset = static_cast<float>(kBlnBaselineOffset);
799 float scale = kBlnXHeight / x_height;
800 if (row == nullptr) {
801 word_middle = word_box.left();
802 input_y_offset = word_box.bottom();
803 final_y_offset = 0.0f;
804 } else {
805 input_y_offset = row->base_line(word_middle) + baseline_shift;
806 }
807 for (int b = 0; b < blobs.size(); ++b) {
808 TBLOB* blob = blobs[b];
809 TBOX blob_box = blob->bounding_box();
810 float mid_x = (blob_box.left() + blob_box.right()) / 2.0f;
811 float baseline = input_y_offset;
812 float blob_scale = scale;
813 if (numeric_mode) {
814 baseline = blob_box.bottom();
815 blob_scale = ClipToRange(kBlnXHeight * 4.0f / (3 * blob_box.height()),
816 scale, scale * 1.5f);
817 } else if (row != nullptr) {
818 baseline = row->base_line(mid_x) + baseline_shift;
819 }
820 // The image will be 8-bit grey if the input was grey or color. Note that in
821 // a grey image 0 is black and 255 is white. If the input was binary, then
822 // the pix will be binary and 0 is white, with 1 being black.
823 // To tell the difference pixGetDepth() will return 8 or 1.
824 // The inverse flag will be true iff the word has been determined to be
825 // white on black, and is independent of whether the pix is 8 bit or 1 bit.
826 blob->Normalize(block, nullptr, nullptr, word_middle, baseline, blob_scale,
827 blob_scale, 0.0f, final_y_offset, inverse, pix);
828 }
829 if (word_denorm != nullptr) {
830 word_denorm->SetupNormalization(block, nullptr, nullptr, word_middle,
831 input_y_offset, scale, scale, 0.0f,
832 final_y_offset);
833 word_denorm->set_inverse(inverse);
834 word_denorm->set_pix(pix);
835 }
836}
837
838// Copies the data and the blobs, but leaves next untouched.
839void TWERD::CopyFrom(const TWERD& src) {
840 Clear();
842 for (int b = 0; b < src.blobs.size(); ++b) {
843 auto* new_blob = new TBLOB(*src.blobs[b]);
844 blobs.push_back(new_blob);
845 }
846}
847
848// Deletes owned data.
851 blobs.clear();
852}
853
854// Recomputes the bounding boxes of the blobs.
856 for (int b = 0; b < blobs.size(); ++b) {
857 blobs[b]->ComputeBoundingBoxes();
858 }
859}
860
862 TBOX result;
863 for (int b = 0; b < blobs.size(); ++b) {
864 TBOX box = blobs[b]->bounding_box();
865 result += box;
866 }
867 return result;
868}
869
870// Merges the blobs from start to end, not including end, and deletes
871// the blobs between start and end.
872void TWERD::MergeBlobs(int start, int end) {
873 if (start >= blobs.size() - 1) return; // Nothing to do.
874 TESSLINE* outline = blobs[start]->outlines;
875 for (int i = start + 1; i < end && i < blobs.size(); ++i) {
876 TBLOB* next_blob = blobs[i];
877 // Take the outlines from the next blob.
878 if (outline == nullptr) {
879 blobs[start]->outlines = next_blob->outlines;
880 outline = blobs[start]->outlines;
881 } else {
882 while (outline->next != nullptr) outline = outline->next;
883 outline->next = next_blob->outlines;
884 next_blob->outlines = nullptr;
885 }
886 // Delete the next blob and move on.
887 delete next_blob;
888 blobs[i] = nullptr;
889 }
890 // Remove dead blobs from the vector.
891 for (int i = start + 1; i < end && start + 1 < blobs.size(); ++i) {
892 blobs.remove(start + 1);
893 }
894}
895
896#ifndef GRAPHICS_DISABLED
897void TWERD::plot(ScrollView* window) {
899 for (int b = 0; b < blobs.size(); ++b) {
900 blobs[b]->plot(window, color, ScrollView::BROWN);
901 color = WERD::NextColor(color);
902 }
903}
904#endif // GRAPHICS_DISABLED
905
906/**********************************************************************
907 * divisible_blob
908 *
909 * Returns true if the blob contains multiple outlines than can be
910 * separated using divide_blobs. Sets the location to be used in the
911 * call to divide_blobs.
912 **********************************************************************/
913bool divisible_blob(TBLOB* blob, bool italic_blob, TPOINT* location) {
914 if (blob->outlines == nullptr || blob->outlines->next == nullptr)
915 return false; // Need at least 2 outlines for it to be possible.
916 int max_gap = 0;
917 TPOINT vertical =
919 for (TESSLINE* outline1 = blob->outlines; outline1 != nullptr;
920 outline1 = outline1->next) {
921 if (outline1->is_hole) continue; // Holes do not count as separable.
922 TPOINT mid_pt1(
923 static_cast<int16_t>((outline1->topleft.x + outline1->botright.x) / 2),
924 static_cast<int16_t>((outline1->topleft.y + outline1->botright.y) / 2));
925 int mid_prod1 = mid_pt1.cross(vertical);
926 int min_prod1, max_prod1;
927 outline1->MinMaxCrossProduct(vertical, &min_prod1, &max_prod1);
928 for (TESSLINE* outline2 = outline1->next; outline2 != nullptr;
929 outline2 = outline2->next) {
930 if (outline2->is_hole) continue; // Holes do not count as separable.
931 TPOINT mid_pt2(static_cast<int16_t>(
932 (outline2->topleft.x + outline2->botright.x) / 2),
933 static_cast<int16_t>(
934 (outline2->topleft.y + outline2->botright.y) / 2));
935 int mid_prod2 = mid_pt2.cross(vertical);
936 int min_prod2, max_prod2;
937 outline2->MinMaxCrossProduct(vertical, &min_prod2, &max_prod2);
938 int mid_gap = abs(mid_prod2 - mid_prod1);
939 int overlap =
940 std::min(max_prod1, max_prod2) - std::max(min_prod1, min_prod2);
941 if (mid_gap - overlap / 4 > max_gap) {
942 max_gap = mid_gap - overlap / 4;
943 *location = mid_pt1;
944 *location += mid_pt2;
945 *location /= 2;
946 }
947 }
948 }
949 // Use the y component of the vertical vector as an approximation to its
950 // length.
951 return max_gap > vertical.y;
952}
953
954/**********************************************************************
955 * divide_blobs
956 *
957 * Create two blobs by grouping the outlines in the appropriate blob.
958 * The outlines that are beyond the location point are moved to the
959 * other blob. The ones whose x location is less than that point are
960 * retained in the original blob.
961 **********************************************************************/
962void divide_blobs(TBLOB* blob, TBLOB* other_blob, bool italic_blob,
963 const TPOINT& location) {
964 TPOINT vertical =
966 TESSLINE* outline1 = nullptr;
967 TESSLINE* outline2 = nullptr;
968
969 TESSLINE* outline = blob->outlines;
970 blob->outlines = nullptr;
971 int location_prod = location.cross(vertical);
972
973 while (outline != nullptr) {
974 TPOINT mid_pt(
975 static_cast<int16_t>((outline->topleft.x + outline->botright.x) / 2),
976 static_cast<int16_t>((outline->topleft.y + outline->botright.y) / 2));
977 int mid_prod = mid_pt.cross(vertical);
978 if (mid_prod < location_prod) {
979 // Outline is in left blob.
980 if (outline1)
981 outline1->next = outline;
982 else
983 blob->outlines = outline;
984 outline1 = outline;
985 } else {
986 // Outline is in right blob.
987 if (outline2)
988 outline2->next = outline;
989 else
990 other_blob->outlines = outline;
991 outline2 = outline;
992 }
993 outline = outline->next;
994 }
995
996 if (outline1) outline1->next = nullptr;
997 if (outline2) outline2->next = nullptr;
998}
const TPOINT kDivisibleVerticalUpright(0, 1)
const TPOINT kDivisibleVerticalItalic(1, 5)
void divide_blobs(TBLOB *blob, TBLOB *other_blob, bool italic_blob, const TPOINT &location)
Definition: blobs.cpp:962
bool divisible_blob(TBLOB *blob, bool italic_blob, TPOINT *location)
Definition: blobs.cpp:913
const int kBlnBaselineOffset
Definition: normalis.h:25
const int kBlnXHeight
Definition: normalis.h:24
TESSLINE * ApproximateOutline(bool allow_detailed_fx, C_OUTLINE *c_outline)
Definition: polyaprx.cpp:61
@ W_SCRIPT_IS_LATIN
Special case latin for y. splitting.
Definition: werd.h:36
#define CLISTIZE(CLASSNAME)
Definition: clst.h:891
void UpdateRange(const T1 &x, T2 *lower_bound, T2 *upper_bound)
Definition: helpers.h:120
int IntCastRounded(double x)
Definition: helpers.h:175
void Swap(T *p1, T *p2)
Definition: helpers.h:95
T ClipToRange(const T &x, const T &lower_bound, const T &upper_bound)
Definition: helpers.h:108
@ baseline
Definition: mfoutline.h:63
void init_to_size(int size, const T &t)
int push_back(T object)
int size() const
Definition: genericvector.h:72
void remove(int index)
void delete_data_pointers()
Definition: blobs.h:51
int16_t x
Definition: blobs.h:93
int16_t y
Definition: blobs.h:94
int cross(const TPOINT &other) const
Definition: blobs.h:79
Definition: blobs.h:99
int start_step
Definition: blobs.h:196
EDGEPT * next
Definition: blobs.h:192
int step_count
Definition: blobs.h:197
C_OUTLINE * src_outline
Definition: blobs.h:194
bool IsHidden() const
Definition: blobs.h:176
VECTOR vec
Definition: blobs.h:187
EDGEPT * prev
Definition: blobs.h:193
TPOINT pos
Definition: blobs.h:186
void Move(const ICOORD vec)
Definition: blobs.cpp:179
EDGEPT * FindBestStartPt() const
Definition: blobs.cpp:283
EDGEPT * loop
Definition: blobs.h:280
TESSLINE * next
Definition: blobs.h:281
void Scale(float factor)
Definition: blobs.cpp:190
TPOINT topleft
Definition: blobs.h:276
void Rotate(const FCOORD rotation)
Definition: blobs.cpp:165
void plot(ScrollView *window, ScrollView::Color color, ScrollView::Color child_color)
Definition: blobs.cpp:262
static TESSLINE * BuildFromOutlineList(EDGEPT *outline)
Definition: blobs.cpp:94
void Clear()
Definition: blobs.cpp:142
TESSLINE()
Definition: blobs.h:204
TPOINT start
Definition: blobs.h:278
void MinMaxCrossProduct(const TPOINT vec, int *min_xp, int *max_xp) const
Definition: blobs.cpp:243
TPOINT botright
Definition: blobs.h:277
void SetupFromPos()
Definition: blobs.cpp:201
bool is_hole
Definition: blobs.h:279
void ComputeBoundingBox()
Definition: blobs.cpp:213
void CopyFrom(const TESSLINE &src)
Definition: blobs.cpp:115
TBOX bounding_box() const
Definition: blobs.cpp:257
void Normalize(const DENORM &denorm)
Definition: blobs.cpp:155
Definition: blobs.h:284
void CorrectBlobOrder(TBLOB *next)
Definition: blobs.cpp:501
void Move(const ICOORD vec)
Definition: blobs.cpp:430
void Clear()
Definition: blobs.cpp:386
TESSLINE * outlines
Definition: blobs.h:400
TBOX bounding_box() const
Definition: blobs.cpp:468
void GetEdgeCoords(const TBOX &box, GenericVector< GenericVector< int > > *x_coords, GenericVector< GenericVector< int > > *y_coords) const
Definition: blobs.cpp:557
int ComputeMoments(FCOORD *center, FCOORD *second_moments) const
Definition: blobs.cpp:522
void Rotate(const FCOORD rotation)
Definition: blobs.cpp:422
void Normalize(const BLOCK *block, const FCOORD *rotation, const DENORM *predecessor, float x_origin, float y_origin, float x_scale, float y_scale, float final_xshift, float final_yshift, bool inverse, Pix *pix)
Definition: blobs.cpp:397
void EliminateDuplicateOutlines()
Definition: blobs.cpp:480
void ComputeBoundingBoxes()
Definition: blobs.cpp:446
void CopyFrom(const TBLOB &src)
Definition: blobs.cpp:370
static TBLOB * PolygonalCopy(bool allow_detailed_fx, C_BLOB *src)
Definition: blobs.cpp:327
void GetPreciseBoundingBox(TBOX *precise_box) const
Definition: blobs.cpp:541
TBLOB * ClassifyNormalizeIfNeeded() const
Definition: blobs.cpp:346
static TBLOB * ShallowCopy(const TBLOB &src)
Definition: blobs.cpp:335
void Scale(float factor)
Definition: blobs.cpp:438
TBLOB()
Definition: blobs.h:285
int NumOutlines() const
Definition: blobs.cpp:454
void plot(ScrollView *window, ScrollView::Color color, ScrollView::Color child_color)
Definition: blobs.cpp:510
Definition: blobs.h:418
void MergeBlobs(int start, int end)
Definition: blobs.cpp:872
TWERD()
Definition: blobs.h:419
void BLNormalize(const BLOCK *block, const ROW *row, Pix *pix, bool inverse, float x_height, float baseline_shift, bool numeric_mode, tesseract::OcrEngineMode hint, const TBOX *norm_box, DENORM *word_denorm)
Definition: blobs.cpp:790
bool latin_script
Definition: blobs.h:460
GenericVector< TBLOB * > blobs
Definition: blobs.h:459
void CopyFrom(const TWERD &src)
Definition: blobs.cpp:839
void Clear()
Definition: blobs.cpp:849
void ComputeBoundingBoxes()
Definition: blobs.cpp:855
TBOX bounding_box() const
Definition: blobs.cpp:861
static TWERD * PolygonalCopy(bool allow_detailed_fx, WERD *src)
Definition: blobs.cpp:776
void plot(ScrollView *window)
Definition: blobs.cpp:897
C_OUTLINE_LIST * child()
Definition: coutln.h:108
ICOORD step(int index) const
Definition: coutln.h:144
void plot(ScrollView *window, ScrollView::Color colour) const
Definition: coutln.cpp:942
int edge_strength_at_index(int index) const
Definition: coutln.h:187
ICOORD position_at_index(int index) const
Definition: coutln.h:153
FCOORD sub_pixel_pos_at_index(const ICOORD &pos, int index) const
Definition: coutln.h:163
int32_t pathlength() const
Definition: coutln.h:135
Definition: linlsq.h:28
double y_variance() const
Definition: linlsq.h:87
double x_variance() const
Definition: linlsq.h:81
void add(double x, double y)
Definition: linlsq.cpp:48
int32_t count() const
Definition: linlsq.h:43
FCOORD mean_point() const
Definition: linlsq.cpp:166
void set_pix(Pix *pix)
Definition: normalis.h:249
void LocalNormBlob(TBLOB *blob) const
Definition: normalis.cpp:412
void set_inverse(bool value)
Definition: normalis.h:255
bool inverse() const
Definition: normalis.h:252
Pix * pix() const
Definition: normalis.h:246
const DENORM * RootDenorm() const
Definition: normalis.h:258
void NormTransform(const DENORM *first_norm, const TPOINT &pt, TPOINT *transformed) const
Definition: normalis.cpp:335
void LocalNormTransform(const TPOINT &pt, TPOINT *transformed) const
Definition: normalis.cpp:306
void SetupNormalization(const BLOCK *block, const FCOORD *rotation, const DENORM *predecessor, float x_origin, float y_origin, float x_scale, float y_scale, float final_xshift, float final_yshift)
Definition: normalis.cpp:96
const BLOCK * block() const
Definition: normalis.h:273
Definition: ocrblock.h:31
FCOORD classify_rotation() const
Definition: ocrblock.h:140
Definition: ocrrow.h:37
float base_line(float xpos) const
Definition: ocrrow.h:59
integer coordinate
Definition: points.h:32
int16_t y() const
access_function
Definition: points.h:56
int16_t x() const
access function
Definition: points.h:52
Definition: points.h:189
float y() const
Definition: points.h:210
void set_y(float yin)
rewrite function
Definition: points.h:218
void set_x(float xin)
rewrite function
Definition: points.h:214
float x() const
Definition: points.h:207
Definition: rect.h:34
const ICOORD & botleft() const
Definition: rect.h:92
int16_t top() const
Definition: rect.h:58
void move(const ICOORD vec)
Definition: rect.h:157
int16_t width() const
Definition: rect.h:115
int16_t height() const
Definition: rect.h:108
int x_middle() const
Definition: rect.h:85
int16_t left() const
Definition: rect.h:72
int16_t bottom() const
Definition: rect.h:65
int16_t right() const
Definition: rect.h:79
C_OUTLINE_LIST * out_list()
Definition: stepblob.h:70
Definition: werd.h:56
static ScrollView::Color NextColor(ScrollView::Color colour)
Definition: werd.cpp:292
C_BLOB_LIST * cblob_list()
Definition: werd.h:95
bool flag(WERD_FLAGS mask) const
Definition: werd.h:117
void DrawTo(int x, int y)
Definition: scrollview.cpp:525
void SetCursor(int x, int y)
Definition: scrollview.cpp:519
void Pen(Color color)
Definition: scrollview.cpp:719