1 /**********************************************************************
2  * File:        scanedg.cpp  (Formerly scanedge.c)
3  * Description: Raster scanning crack based edge extractor.
4  * Author:      Ray Smith
5  *
6  * (C) Copyright 1991, Hewlett-Packard Ltd.
7  ** Licensed under the Apache License, Version 2.0 (the "License");
8  ** you may not use this file except in compliance with the License.
9  ** You may obtain a copy of the License at
10  ** http://www.apache.org/licenses/LICENSE-2.0
11  ** Unless required by applicable law or agreed to in writing, software
12  ** distributed under the License is distributed on an "AS IS" BASIS,
13  ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  ** See the License for the specific language governing permissions and
15  ** limitations under the License.
16  *
17  **********************************************************************/
18 
19 #include "scanedg.h"
20 
21 #include "crakedge.h"
22 #include "edgloop.h"
23 #include "pdblock.h"
24 
25 #include <allheaders.h>
26 
27 #include <memory> // std::unique_ptr
28 
29 namespace tesseract {
30 
31 #define WHITE_PIX 1 /*thresholded colours */
32 #define BLACK_PIX 0
33 // Flips between WHITE_PIX and BLACK_PIX.
34 #define FLIP_COLOUR(pix) (1 - (pix))
35 
36 struct CrackPos {
37   CRACKEDGE **free_cracks; // Freelist for fast allocation.
38   int x;                   // Position of new edge.
39   int y;
40 };
41 
42 static void free_crackedges(CRACKEDGE *start);
43 
44 static void join_edges(CRACKEDGE *edge1, CRACKEDGE *edge2, CRACKEDGE **free_cracks,
45                        C_OUTLINE_IT *outline_it);
46 
47 static void line_edges(TDimension x, TDimension y, TDimension xext, uint8_t uppercolour, uint8_t *bwpos,
48                        CRACKEDGE **prevline, CRACKEDGE **free_cracks, C_OUTLINE_IT *outline_it);
49 
50 static void make_margins(PDBLK *block, BLOCK_LINE_IT *line_it, uint8_t *pixels, uint8_t margin,
51                          TDimension left, TDimension right, TDimension y);
52 
53 static CRACKEDGE *h_edge(int sign, CRACKEDGE *join, CrackPos *pos);
54 static CRACKEDGE *v_edge(int sign, CRACKEDGE *join, CrackPos *pos);
55 
56 /**********************************************************************
57  * block_edges
58  *
59  * Extract edges from a PDBLK.
60  **********************************************************************/
61 
block_edges(Image t_pix,PDBLK * block,C_OUTLINE_IT * outline_it)62 void block_edges(Image t_pix,   // thresholded image
63                  PDBLK *block, // block in image
64                  C_OUTLINE_IT *outline_it) {
65   ICOORD bleft; // bounding box
66   ICOORD tright;
67   BLOCK_LINE_IT line_it = block; // line iterator
68 
69   int width = pixGetWidth(t_pix);
70   int height = pixGetHeight(t_pix);
71   int wpl = pixGetWpl(t_pix);
72   // lines in progress
73   std::unique_ptr<CRACKEDGE *[]> ptrline(new CRACKEDGE *[width + 1]);
74   CRACKEDGE *free_cracks = nullptr;
75 
76   block->bounding_box(bleft, tright); // block box
77   ASSERT_HOST(tright.x() <= width);
78   ASSERT_HOST(tright.y() <= height);
79   int block_width = tright.x() - bleft.x();
80   for (int x = block_width; x >= 0; x--) {
81     ptrline[x] = nullptr; //  no lines in progress
82   }
83 
84   std::unique_ptr<uint8_t[]> bwline(new uint8_t[width]);
85 
86   const uint8_t margin = WHITE_PIX;
87 
88   for (int y = tright.y() - 1; y >= bleft.y() - 1; y--) {
89     if (y >= bleft.y() && y < tright.y()) {
90       // Get the binary pixels from the image.
91       l_uint32 *line = pixGetData(t_pix) + wpl * (height - 1 - y);
92       for (int x = 0; x < block_width; ++x) {
93         bwline[x] = GET_DATA_BIT(line, x + bleft.x()) ^ 1;
94       }
95       make_margins(block, &line_it, bwline.get(), margin, bleft.x(), tright.x(), y);
96     } else {
97       memset(bwline.get(), margin, block_width * sizeof(bwline[0]));
98     }
99     line_edges(bleft.x(), y, block_width, margin, bwline.get(), ptrline.get(), &free_cracks,
100                outline_it);
101   }
102 
103   free_crackedges(free_cracks); // really free them
104 }
105 
106 /**********************************************************************
107  * make_margins
108  *
109  * Get an image line and set to margin non-text pixels.
110  **********************************************************************/
111 
make_margins(PDBLK * block,BLOCK_LINE_IT * line_it,uint8_t * pixels,uint8_t margin,TDimension left,TDimension right,TDimension y)112 static void make_margins(   // get a line
113     PDBLK *block,           // block in image
114     BLOCK_LINE_IT *line_it, // for old style
115     uint8_t *pixels,        // pixels to strip
116     uint8_t margin,         // white-out pixel
117     TDimension left,        // block edges
118     TDimension right,
119     TDimension y            // line coord
120 ) {
121   ICOORDELT_IT seg_it;
122 
123   if (block->poly_block() != nullptr) {
124     std::unique_ptr<PB_LINE_IT> lines(new PB_LINE_IT(block->poly_block()));
125     const std::unique_ptr</*non-const*/ ICOORDELT_LIST> segments(lines->get_line(y));
126     if (!segments->empty()) {
127       seg_it.set_to_list(segments.get());
128       seg_it.mark_cycle_pt();
129       auto start = seg_it.data()->x();
130       auto xext = seg_it.data()->y();
131       for (auto xindex = left; xindex < right; xindex++) {
132         if (xindex >= start && !seg_it.cycled_list()) {
133           xindex = start + xext - 1;
134           seg_it.forward();
135           start = seg_it.data()->x();
136           xext = seg_it.data()->y();
137         } else {
138           pixels[xindex - left] = margin;
139         }
140       }
141     } else {
142       for (auto xindex = left; xindex < right; xindex++) {
143         pixels[xindex - left] = margin;
144       }
145     }
146   } else {
147     TDimension xext;  // of segment
148     auto start = line_it->get_line(y, xext);
149     for (auto xindex = left; xindex < start; xindex++) {
150       pixels[xindex - left] = margin;
151     }
152     for (auto xindex = start + xext; xindex < right; xindex++) {
153       pixels[xindex - left] = margin;
154     }
155   }
156 }
157 
158 /**********************************************************************
159  * line_edges
160  *
161  * Scan a line for edges and update the edges in progress.
162  * When edges close into loops, send them for approximation.
163  **********************************************************************/
164 
line_edges(TDimension x,TDimension y,TDimension xext,uint8_t uppercolour,uint8_t * bwpos,CRACKEDGE ** prevline,CRACKEDGE ** free_cracks,C_OUTLINE_IT * outline_it)165 static void line_edges(TDimension x,         // coord of line start
166                        TDimension y,         // coord of line
167                        TDimension xext,      // width of line
168                        uint8_t uppercolour,  // start of prev line
169                        uint8_t *bwpos,       // thresholded line
170                        CRACKEDGE **prevline, // edges in progress
171                        CRACKEDGE **free_cracks, C_OUTLINE_IT *outline_it) {
172   CrackPos pos = {free_cracks, x, y};
173   int xmax;              // max x coord
174   int prevcolour;        // of previous pixel
175   CRACKEDGE *current;    // current h edge
176   CRACKEDGE *newcurrent; // new h edge
177 
178   xmax = x + xext;          // max allowable coord
179   prevcolour = uppercolour; // forced plain margin
180   current = nullptr;        // nothing yet
181 
182   // do each pixel
183   for (; pos.x < xmax; pos.x++, prevline++) {
184     const int colour = *bwpos++; // current pixel
185     if (*prevline != nullptr) {
186       // changed above
187       // change colour
188       uppercolour = FLIP_COLOUR(uppercolour);
189       if (colour == prevcolour) {
190         if (colour == uppercolour) {
191           // finish a line
192           join_edges(current, *prevline, free_cracks, outline_it);
193           current = nullptr; // no edge now
194         } else {
195           // new horiz edge
196           current = h_edge(uppercolour - colour, *prevline, &pos);
197         }
198         *prevline = nullptr; // no change this time
199       } else {
200         if (colour == uppercolour) {
201           *prevline = v_edge(colour - prevcolour, *prevline, &pos);
202         // 8 vs 4 connection
203         } else if (colour == WHITE_PIX) {
204           join_edges(current, *prevline, free_cracks, outline_it);
205           current = h_edge(uppercolour - colour, nullptr, &pos);
206           *prevline = v_edge(colour - prevcolour, current, &pos);
207         } else {
208           newcurrent = h_edge(uppercolour - colour, *prevline, &pos);
209           *prevline = v_edge(colour - prevcolour, current, &pos);
210           current = newcurrent; // right going h edge
211         }
212         prevcolour = colour; // remember new colour
213       }
214     } else {
215       if (colour != prevcolour) {
216         *prevline = current = v_edge(colour - prevcolour, current, &pos);
217         prevcolour = colour;
218       }
219       if (colour != uppercolour) {
220         current = h_edge(uppercolour - colour, current, &pos);
221       } else {
222         current = nullptr; // no edge now
223       }
224     }
225   }
226   if (current != nullptr) {
227     // out of block
228     if (*prevline != nullptr) { // got one to join to?
229       join_edges(current, *prevline, free_cracks, outline_it);
230       *prevline = nullptr; // tidy now
231     } else {
232       // fake vertical
233       *prevline = v_edge(FLIP_COLOUR(prevcolour) - prevcolour, current, &pos);
234     }
235   } else if (*prevline != nullptr) {
236     // continue fake
237     *prevline = v_edge(FLIP_COLOUR(prevcolour) - prevcolour, *prevline, &pos);
238   }
239 }
240 
241 /**********************************************************************
242  * h_edge
243  *
244  * Create a new horizontal CRACKEDGE and join it to the given edge.
245  **********************************************************************/
246 
h_edge(int sign,CRACKEDGE * join,CrackPos * pos)247 static CRACKEDGE *h_edge(int sign,        // sign of edge
248                          CRACKEDGE *join, // edge to join to
249                          CrackPos *pos) {
250   CRACKEDGE *newpt; // return value
251 
252   if (*pos->free_cracks != nullptr) {
253     newpt = *pos->free_cracks;
254     *pos->free_cracks = newpt->next; // get one fast
255   } else {
256     newpt = new CRACKEDGE;
257   }
258   newpt->pos.set_y(pos->y + 1); // coords of pt
259   newpt->stepy = 0;             // edge is horizontal
260 
261   if (sign > 0) {
262     newpt->pos.set_x(pos->x + 1); // start location
263     newpt->stepx = -1;
264     newpt->stepdir = 0;
265   } else {
266     newpt->pos.set_x(pos->x); // start location
267     newpt->stepx = 1;
268     newpt->stepdir = 2;
269   }
270 
271   if (join == nullptr) {
272     newpt->next = newpt; // ptrs to other ends
273     newpt->prev = newpt;
274   } else {
275     if (newpt->pos.x() + newpt->stepx == join->pos.x() && newpt->pos.y() == join->pos.y()) {
276       newpt->prev = join->prev; // update other ends
277       newpt->prev->next = newpt;
278       newpt->next = join; // join up
279       join->prev = newpt;
280     } else {
281       newpt->next = join->next; // update other ends
282       newpt->next->prev = newpt;
283       newpt->prev = join; // join up
284       join->next = newpt;
285     }
286   }
287   return newpt;
288 }
289 
290 /**********************************************************************
291  * v_edge
292  *
293  * Create a new vertical CRACKEDGE and join it to the given edge.
294  **********************************************************************/
295 
v_edge(int sign,CRACKEDGE * join,CrackPos * pos)296 static CRACKEDGE *v_edge(int sign, // sign of edge
297                          CRACKEDGE *join, CrackPos *pos) {
298   CRACKEDGE *newpt; // return value
299 
300   if (*pos->free_cracks != nullptr) {
301     newpt = *pos->free_cracks;
302     *pos->free_cracks = newpt->next; // get one fast
303   } else {
304     newpt = new CRACKEDGE;
305   }
306   newpt->pos.set_x(pos->x); // coords of pt
307   newpt->stepx = 0;         // edge is vertical
308 
309   if (sign > 0) {
310     newpt->pos.set_y(pos->y); // start location
311     newpt->stepy = 1;
312     newpt->stepdir = 3;
313   } else {
314     newpt->pos.set_y(pos->y + 1); // start location
315     newpt->stepy = -1;
316     newpt->stepdir = 1;
317   }
318 
319   if (join == nullptr) {
320     newpt->next = newpt; // ptrs to other ends
321     newpt->prev = newpt;
322   } else {
323     if (newpt->pos.x() == join->pos.x() && newpt->pos.y() + newpt->stepy == join->pos.y()) {
324       newpt->prev = join->prev; // update other ends
325       newpt->prev->next = newpt;
326       newpt->next = join; // join up
327       join->prev = newpt;
328     } else {
329       newpt->next = join->next; // update other ends
330       newpt->next->prev = newpt;
331       newpt->prev = join; // join up
332       join->next = newpt;
333     }
334   }
335   return newpt;
336 }
337 
338 /**********************************************************************
339  * join_edges
340  *
341  * Join 2 edges together. Send the outline for approximation when a
342  * closed loop is formed.
343  **********************************************************************/
344 
join_edges(CRACKEDGE * edge1,CRACKEDGE * edge2,CRACKEDGE ** free_cracks,C_OUTLINE_IT * outline_it)345 static void join_edges(CRACKEDGE *edge1, // edges to join
346                        CRACKEDGE *edge2, // no specific order
347                        CRACKEDGE **free_cracks, C_OUTLINE_IT *outline_it) {
348   if (edge1->pos.x() + edge1->stepx != edge2->pos.x() ||
349       edge1->pos.y() + edge1->stepy != edge2->pos.y()) {
350     CRACKEDGE *tempedge = edge1;
351     edge1 = edge2; // swap around
352     edge2 = tempedge;
353   }
354 
355   if (edge1->next == edge2) {
356     // already closed
357     complete_edge(edge1, outline_it);
358     // attach freelist to end
359     edge1->prev->next = *free_cracks;
360     *free_cracks = edge1; // and free list
361   } else {
362     // update opposite ends
363     edge2->prev->next = edge1->next;
364     edge1->next->prev = edge2->prev;
365     edge1->next = edge2; // make joins
366     edge2->prev = edge1;
367   }
368 }
369 
370 /**********************************************************************
371  * free_crackedges
372  *
373  * Really free the CRACKEDGEs by giving them back to delete.
374  **********************************************************************/
375 
free_crackedges(CRACKEDGE * start)376 static void free_crackedges(CRACKEDGE *start) {
377   CRACKEDGE *current; // current edge to free
378   CRACKEDGE *next;    // next one to free
379 
380   for (current = start; current != nullptr; current = next) {
381     next = current->next;
382     delete current; // delete them all
383   }
384 }
385 
386 } // namespace tesseract
387