comparison mupdf-source/thirdparty/leptonica/src/selgen.c @ 2:b50eed0cc0ef upstream

ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4. The directory name has changed: no version number in the expanded directory now.
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:43:07 +0200
parents
children
comparison
equal deleted inserted replaced
1:1d09e1dec1d9 2:b50eed0cc0ef
1 /*====================================================================*
2 - Copyright (C) 2001 Leptonica. All rights reserved.
3 -
4 - Redistribution and use in source and binary forms, with or without
5 - modification, are permitted provided that the following conditions
6 - are met:
7 - 1. Redistributions of source code must retain the above copyright
8 - notice, this list of conditions and the following disclaimer.
9 - 2. Redistributions in binary form must reproduce the above
10 - copyright notice, this list of conditions and the following
11 - disclaimer in the documentation and/or other materials
12 - provided with the distribution.
13 -
14 - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15 - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16 - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17 - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
18 - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
23 - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24 - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 *====================================================================*/
26
27 /*!
28 * \file selgen.c
29 * <pre>
30 *
31 * This file contains functions that generate hit-miss Sels
32 * for doing a loose match to a small bitmap. The hit-miss
33 * Sel is made from a given bitmap. Several "knobs"
34 * are available to control the looseness of the match.
35 * In general, a tight match will have fewer false positives
36 * (bad matches) but more false negatives (missed patterns).
37 * The values to be used depend on the quality and variation
38 * of the image in which the pattern is to be searched,
39 * and the relative penalties of false positives and
40 * false negatives. Default values for the three knobs --
41 * minimum distance to boundary pixels, number of extra pixels
42 * added to selected sides, and minimum acceptable runlength
43 * in eroded version -- are provided.
44 *
45 * The generated hit-miss Sels can always be used in the
46 * rasterop implementation of binary morphology (in morph.h).
47 * If they are small enough (not more than 31 pixels extending
48 * in any direction from the Sel origin), they can also be used
49 * to auto-generate dwa code (fmorphauto.c).
50 *
51 * In production, use pixGenerateSelBoundary().
52 *
53 * Generate a subsampled structuring element
54 * SEL *pixGenerateSelBoundary()
55 * SEL *pixGenerateSelWithRuns()
56 * SEL *pixGenerateSelRandom()
57 *
58 * Accumulate data on runs along lines
59 * NUMA *pixGetRunCentersOnLine()
60 * NUMA *pixGetRunsOnLine()
61 *
62 * Subsample boundary pixels in relatively ordered way
63 * PTA *pixSubsampleBoundaryPixels()
64 * PTA *adjacentOnPixelInRaster()
65 *
66 * Display generated sel with originating image
67 * PIX *pixDisplayHitMissSel()
68 * </pre>
69 */
70
71 #ifdef HAVE_CONFIG_H
72 #include <config_auto.h>
73 #endif /* HAVE_CONFIG_H */
74
75 #include "allheaders.h"
76
77 /* Default minimum distance of a hit-miss pixel element to
78 * a boundary pixel of its color. */
79 static const l_int32 DefaultDistanceToBoundary = 1;
80 static const l_int32 MaxDistanceToBoundary = 4;
81
82 /* Default min runlength to accept a hit or miss element located
83 * at its center */
84 static const l_int32 DefaultMinRunlength = 3;
85
86 /* Default scalefactor for displaying image and hit-miss sel
87 * that is derived from it */
88 static const l_int32 DefaultSelScalefactor = 7;
89 static const l_int32 MaxSelScalefactor = 31; /* should be big enough */
90
91 #ifndef NO_CONSOLE_IO
92 #define DEBUG_DISPLAY_HM_SEL 0
93 #endif /* ~NO_CONSOLE_IO */
94
95
96 /*-----------------------------------------------------------------*
97 * Generate a subsampled structuring element *
98 *-----------------------------------------------------------------*/
99
100 /*!
101 * \brief pixGenerateSelBoundary()
102 *
103 * \param[in] pixs 1 bpp, typically small, to be used as a pattern
104 * \param[in] hitdist min distance from fg boundary pixel
105 * \param[in] missdist min distance from bg boundary pixel
106 * \param[in] hitskip number of boundary pixels skipped between hits
107 * \param[in] missskip number of boundary pixels skipped between misses
108 * \param[in] topflag flag for extra pixels of bg added above
109 * \param[in] botflag flag for extra pixels of bg added below
110 * \param[in] leftflag flag for extra pixels of bg added to left
111 * \param[in] rightflag flag for extra pixels of bg added to right
112 * \param[out] ppixe [optional] input pix expanded by extra pixels
113 * \return sel hit-miss for input pattern, or NULL on error
114 *
115 * <pre>
116 * Notes:
117 * (1) All fg elements selected are exactly hitdist pixels away from
118 * the nearest fg boundary pixel, and ditto for bg elements.
119 * Valid inputs of hitdist and missdist are 0, 1, 2, 3 and 4.
120 * For example, a hitdist of 0 puts the hits at the fg boundary.
121 * Usually, the distances should be > 0 avoid the effect of
122 * noise at the boundary.
123 * (2) Set hitskip < 0 if no hits are to be used. Ditto for missskip.
124 * If both hitskip and missskip are < 0, the sel would be empty,
125 * and NULL is returned.
126 * (3) The 4 flags determine whether the sel is increased on that side
127 * to allow bg misses to be placed all along that boundary.
128 * The increase in sel size on that side is the minimum necessary
129 * to allow the misses to be placed at mindist. For text characters,
130 * the topflag and botflag are typically set to 1, and the leftflag
131 * and rightflag to 0.
132 * (4) The input pix, as extended by the extra pixels on selected sides,
133 * can optionally be returned. For debugging, call
134 * pixDisplayHitMissSel() to visualize the hit-miss sel superimposed
135 * on the generating bitmap.
136 * (5) This is the best of the three sel generators: it gives the most
137 * flexibility with the smallest number of hits and misses.
138 * The other two generators are presented as examples of other
139 * approaches, but they should not be used in production.
140 * </pre>
141 */
142 SEL *
143 pixGenerateSelBoundary(PIX *pixs,
144 l_int32 hitdist,
145 l_int32 missdist,
146 l_int32 hitskip,
147 l_int32 missskip,
148 l_int32 topflag,
149 l_int32 botflag,
150 l_int32 leftflag,
151 l_int32 rightflag,
152 PIX **ppixe)
153 {
154 l_int32 ws, hs, w, h, x, y, ix, iy, i, npt;
155 PIX *pixt1, *pixt2, *pixt3, *pixfg, *pixbg;
156 SEL *selh, *selm, *sel_3, *sel;
157 PTA *ptah = NULL, *ptam = NULL;
158
159 if (ppixe) *ppixe = NULL;
160 if (!pixs)
161 return (SEL *)ERROR_PTR("pixs not defined", __func__, NULL);
162 if (pixGetDepth(pixs) != 1)
163 return (SEL *)ERROR_PTR("pixs not 1 bpp", __func__, NULL);
164 if (hitdist < 0 || hitdist > 4 || missdist < 0 || missdist > 4)
165 return (SEL *)ERROR_PTR("dist not in {0 .. 4}", __func__, NULL);
166 if (hitskip < 0 && missskip < 0)
167 return (SEL *)ERROR_PTR("no hits or misses", __func__, NULL);
168
169 /* Locate the foreground */
170 pixClipToForeground(pixs, &pixt1, NULL);
171 if (!pixt1)
172 return (SEL *)ERROR_PTR("pixt1 not made", __func__, NULL);
173 ws = pixGetWidth(pixt1);
174 hs = pixGetHeight(pixt1);
175 w = ws;
176 h = hs;
177
178 /* Crop out a region including the foreground, and add pixels
179 * on sides depending on the side flags */
180 if (topflag || botflag || leftflag || rightflag) {
181 x = y = 0;
182 if (topflag) {
183 h += missdist + 1;
184 y = missdist + 1;
185 }
186 if (botflag)
187 h += missdist + 1;
188 if (leftflag) {
189 w += missdist + 1;
190 x = missdist + 1;
191 }
192 if (rightflag)
193 w += missdist + 1;
194 pixt2 = pixCreate(w, h, 1);
195 pixRasterop(pixt2, x, y, ws, hs, PIX_SRC, pixt1, 0, 0);
196 } else {
197 pixt2 = pixClone(pixt1);
198 }
199 if (ppixe)
200 *ppixe = pixClone(pixt2);
201 pixDestroy(&pixt1);
202
203 /* Identify fg and bg pixels that are exactly hitdist and
204 * missdist (rsp) away from the boundary pixels in their set.
205 * Then get a subsampled set of these points. */
206 sel_3 = selCreateBrick(3, 3, 1, 1, SEL_HIT);
207 if (hitskip >= 0) {
208 selh = selCreateBrick(2 * hitdist + 1, 2 * hitdist + 1,
209 hitdist, hitdist, SEL_HIT);
210 pixt3 = pixErode(NULL, pixt2, selh);
211 pixfg = pixErode(NULL, pixt3, sel_3);
212 pixXor(pixfg, pixfg, pixt3);
213 ptah = pixSubsampleBoundaryPixels(pixfg, hitskip);
214 pixDestroy(&pixt3);
215 pixDestroy(&pixfg);
216 selDestroy(&selh);
217 }
218 if (missskip >= 0) {
219 selm = selCreateBrick(2 * missdist + 1, 2 * missdist + 1,
220 missdist, missdist, SEL_HIT);
221 pixt3 = pixDilate(NULL, pixt2, selm);
222 pixbg = pixDilate(NULL, pixt3, sel_3);
223 pixXor(pixbg, pixbg, pixt3);
224 ptam = pixSubsampleBoundaryPixels(pixbg, missskip);
225 pixDestroy(&pixt3);
226 pixDestroy(&pixbg);
227 selDestroy(&selm);
228 }
229 selDestroy(&sel_3);
230 pixDestroy(&pixt2);
231
232 /* Generate the hit-miss sel from these point */
233 sel = selCreateBrick(h, w, h / 2, w / 2, SEL_DONT_CARE);
234 if (hitskip >= 0) {
235 npt = ptaGetCount(ptah);
236 for (i = 0; i < npt; i++) {
237 ptaGetIPt(ptah, i, &ix, &iy);
238 selSetElement(sel, iy, ix, SEL_HIT);
239 }
240 }
241 if (missskip >= 0) {
242 npt = ptaGetCount(ptam);
243 for (i = 0; i < npt; i++) {
244 ptaGetIPt(ptam, i, &ix, &iy);
245 selSetElement(sel, iy, ix, SEL_MISS);
246 }
247 }
248
249 ptaDestroy(&ptah);
250 ptaDestroy(&ptam);
251 return sel;
252 }
253
254
255 /*!
256 * \brief pixGenerateSelWithRuns()
257 *
258 * \param[in] pixs 1 bpp, typically small, to be used as a pattern
259 * \param[in] nhlines number of hor lines along which elements are found
260 * \param[in] nvlines number of vert lines along which elements are found
261 * \param[in] distance min distance from boundary pixel; use 0 for default
262 * \param[in] minlength min runlength to set hit or miss; use 0 for default
263 * \param[in] toppix number of extra pixels of bg added above
264 * \param[in] botpix number of extra pixels of bg added below
265 * \param[in] leftpix number of extra pixels of bg added to left
266 * \param[in] rightpix number of extra pixels of bg added to right
267 * \param[out] ppixe [optional] input pix expanded by extra pixels
268 * \return sel hit-miss for input pattern, or NULL on error
269 *
270 * <pre>
271 * Notes:
272 * (1) The horizontal and vertical lines along which elements are
273 * selected are roughly equally spaced. The actual locations of
274 * the hits and misses are the centers of respective run-lengths.
275 * (2) No elements are selected that are less than 'distance' pixels away
276 * from a boundary pixel of the same color. This makes the
277 * match much more robust to edge noise. Valid inputs of
278 * 'distance' are 0, 1, 2, 3 and 4. If distance is either 0 or
279 * greater than 4, we reset it to the default value.
280 * (3) The 4 numbers for adding rectangles of pixels outside the fg
281 * can be use if the pattern is expected to be surrounded by bg
282 * (white) pixels. On the other hand, if the pattern may be near
283 * other fg (black) components on some sides, use 0 for those sides.
284 * (4) The pixels added to a side allow you to have miss elements there.
285 * There is a constraint between distance, minlength, and
286 * the added pixels for this to work. We illustrate using the
287 * default values. If you add 5 pixels to the top, and use a
288 * distance of 1, then you end up with a vertical run of at least
289 * 4 bg pixels along the top edge of the image. If you use a
290 * minimum runlength of 3, each vertical line will always find
291 * a miss near the center of its run. However, if you use a
292 * minimum runlength of 5, you will not get a miss on every vertical
293 * line. As another example, if you have 7 added pixels and a
294 * distance of 2, you can use a runlength up to 5 to guarantee
295 * that the miss element is recorded. We give a warning if the
296 * constraint does not guarantee a miss element outside the
297 * image proper.
298 * (5) The input pix, as extended by the extra pixels on selected sides,
299 * can optionally be returned. For debugging, call
300 * pixDisplayHitMissSel() to visualize the hit-miss sel superimposed
301 * on the generating bitmap.
302 * (6) This method is inferior to the boundary method.
303 * </pre>
304 */
305 SEL *
306 pixGenerateSelWithRuns(PIX *pixs,
307 l_int32 nhlines,
308 l_int32 nvlines,
309 l_int32 distance,
310 l_int32 minlength,
311 l_int32 toppix,
312 l_int32 botpix,
313 l_int32 leftpix,
314 l_int32 rightpix,
315 PIX **ppixe)
316 {
317 l_int32 ws, hs, w, h, x, y, xval, yval, i, j, nh, nm;
318 l_float32 delh, delw;
319 NUMA *nah, *nam;
320 PIX *pixt1, *pixt2, *pixfg, *pixbg;
321 PTA *ptah, *ptam;
322 SEL *seld, *sel;
323
324 if (ppixe) *ppixe = NULL;
325 if (!pixs)
326 return (SEL *)ERROR_PTR("pixs not defined", __func__, NULL);
327 if (pixGetDepth(pixs) != 1)
328 return (SEL *)ERROR_PTR("pixs not 1 bpp", __func__, NULL);
329 if (nhlines < 1 && nvlines < 1)
330 return (SEL *)ERROR_PTR("nvlines and nhlines both < 1", __func__, NULL);
331
332 if (distance <= 0)
333 distance = DefaultDistanceToBoundary;
334 if (minlength <= 0)
335 minlength = DefaultMinRunlength;
336 if (distance > MaxDistanceToBoundary) {
337 L_WARNING("distance too large; setting to max value\n", __func__);
338 distance = MaxDistanceToBoundary;
339 }
340
341 /* Locate the foreground */
342 pixClipToForeground(pixs, &pixt1, NULL);
343 if (!pixt1)
344 return (SEL *)ERROR_PTR("pixt1 not made", __func__, NULL);
345 ws = pixGetWidth(pixt1);
346 hs = pixGetHeight(pixt1);
347 w = ws;
348 h = hs;
349
350 /* Crop out a region including the foreground, and add pixels
351 * on sides depending on the side flags */
352 if (toppix || botpix || leftpix || rightpix) {
353 x = y = 0;
354 if (toppix) {
355 h += toppix;
356 y = toppix;
357 if (toppix < distance + minlength)
358 L_WARNING("no miss elements in added top pixels\n", __func__);
359 }
360 if (botpix) {
361 h += botpix;
362 if (botpix < distance + minlength)
363 L_WARNING("no miss elements in added bot pixels\n", __func__);
364 }
365 if (leftpix) {
366 w += leftpix;
367 x = leftpix;
368 if (leftpix < distance + minlength)
369 L_WARNING("no miss elements in added left pixels\n", __func__);
370 }
371 if (rightpix) {
372 w += rightpix;
373 if (rightpix < distance + minlength)
374 L_WARNING("no miss elements in added right pixels\n", __func__);
375 }
376 pixt2 = pixCreate(w, h, 1);
377 pixRasterop(pixt2, x, y, ws, hs, PIX_SRC, pixt1, 0, 0);
378 } else {
379 pixt2 = pixClone(pixt1);
380 }
381 if (ppixe)
382 *ppixe = pixClone(pixt2);
383 pixDestroy(&pixt1);
384
385 /* Identify fg and bg pixels that are at least 'distance' pixels
386 * away from the boundary pixels in their set */
387 seld = selCreateBrick(2 * distance + 1, 2 * distance + 1,
388 distance, distance, SEL_HIT);
389 pixfg = pixErode(NULL, pixt2, seld);
390 pixbg = pixDilate(NULL, pixt2, seld);
391 pixInvert(pixbg, pixbg);
392 selDestroy(&seld);
393 pixDestroy(&pixt2);
394
395 /* Accumulate hit and miss points */
396 ptah = ptaCreate(0);
397 ptam = ptaCreate(0);
398 if (nhlines >= 1) {
399 delh = (l_float32)h / (l_float32)(nhlines + 1);
400 for (i = 0, y = 0; i < nhlines; i++) {
401 y += (l_int32)(delh + 0.5);
402 nah = pixGetRunCentersOnLine(pixfg, -1, y, minlength);
403 nam = pixGetRunCentersOnLine(pixbg, -1, y, minlength);
404 nh = numaGetCount(nah);
405 nm = numaGetCount(nam);
406 for (j = 0; j < nh; j++) {
407 numaGetIValue(nah, j, &xval);
408 ptaAddPt(ptah, xval, y);
409 }
410 for (j = 0; j < nm; j++) {
411 numaGetIValue(nam, j, &xval);
412 ptaAddPt(ptam, xval, y);
413 }
414 numaDestroy(&nah);
415 numaDestroy(&nam);
416 }
417 }
418 if (nvlines >= 1) {
419 delw = (l_float32)w / (l_float32)(nvlines + 1);
420 for (i = 0, x = 0; i < nvlines; i++) {
421 x += (l_int32)(delw + 0.5);
422 nah = pixGetRunCentersOnLine(pixfg, x, -1, minlength);
423 nam = pixGetRunCentersOnLine(pixbg, x, -1, minlength);
424 nh = numaGetCount(nah);
425 nm = numaGetCount(nam);
426 for (j = 0; j < nh; j++) {
427 numaGetIValue(nah, j, &yval);
428 ptaAddPt(ptah, x, yval);
429 }
430 for (j = 0; j < nm; j++) {
431 numaGetIValue(nam, j, &yval);
432 ptaAddPt(ptam, x, yval);
433 }
434 numaDestroy(&nah);
435 numaDestroy(&nam);
436 }
437 }
438
439 /* Make the Sel with those points */
440 sel = selCreateBrick(h, w, h / 2, w / 2, SEL_DONT_CARE);
441 nh = ptaGetCount(ptah);
442 for (i = 0; i < nh; i++) {
443 ptaGetIPt(ptah, i, &x, &y);
444 selSetElement(sel, y, x, SEL_HIT);
445 }
446 nm = ptaGetCount(ptam);
447 for (i = 0; i < nm; i++) {
448 ptaGetIPt(ptam, i, &x, &y);
449 selSetElement(sel, y, x, SEL_MISS);
450 }
451
452 pixDestroy(&pixfg);
453 pixDestroy(&pixbg);
454 ptaDestroy(&ptah);
455 ptaDestroy(&ptam);
456 return sel;
457 }
458
459
460 /*!
461 * \brief pixGenerateSelRandom()
462 *
463 * \param[in] pixs 1 bpp, typically small, to be used as a pattern
464 * \param[in] hitfract fraction of allowable fg pixels that are hits
465 * \param[in] missfract fraction of allowable bg pixels that are misses
466 * \param[in] distance min distance from boundary pixel; use 0 for default
467 * \param[in] toppix number of extra pixels of bg added above
468 * \param[in] botpix number of extra pixels of bg added below
469 * \param[in] leftpix number of extra pixels of bg added to left
470 * \param[in] rightpix number of extra pixels of bg added to right
471 * \param[out] ppixe [optional] input pix expanded by extra pixels
472 * \return sel hit-miss for input pattern, or NULL on error
473 *
474 * <pre>
475 * Notes:
476 * (1) Either of hitfract and missfract can be zero. If both are zero,
477 * the sel would be empty, and NULL is returned.
478 * (2) No elements are selected that are less than 'distance' pixels away
479 * from a boundary pixel of the same color. This makes the
480 * match much more robust to edge noise. Valid inputs of
481 * 'distance' are 0, 1, 2, 3 and 4. If distance is either 0 or
482 * greater than 4, we reset it to the default value.
483 * (3) The 4 numbers for adding rectangles of pixels outside the fg
484 * can be use if the pattern is expected to be surrounded by bg
485 * (white) pixels. On the other hand, if the pattern may be near
486 * other fg (black) components on some sides, use 0 for those sides.
487 * (4) The input pix, as extended by the extra pixels on selected sides,
488 * can optionally be returned. For debugging, call
489 * pixDisplayHitMissSel() to visualize the hit-miss sel superimposed
490 * on the generating bitmap.
491 * (5) This method is inferior to both the boundary and run based methods.
492 * </pre>
493 */
494 SEL *
495 pixGenerateSelRandom(PIX *pixs,
496 l_float32 hitfract,
497 l_float32 missfract,
498 l_int32 distance,
499 l_int32 toppix,
500 l_int32 botpix,
501 l_int32 leftpix,
502 l_int32 rightpix,
503 PIX **ppixe)
504 {
505 l_int32 ws, hs, w, h, x, y, i, j, thresh;
506 l_uint32 val;
507 PIX *pixt1, *pixt2, *pixfg, *pixbg;
508 SEL *seld, *sel;
509
510 if (ppixe) *ppixe = NULL;
511 if (!pixs)
512 return (SEL *)ERROR_PTR("pixs not defined", __func__, NULL);
513 if (pixGetDepth(pixs) != 1)
514 return (SEL *)ERROR_PTR("pixs not 1 bpp", __func__, NULL);
515 if (hitfract <= 0.0 && missfract <= 0.0)
516 return (SEL *)ERROR_PTR("no hits or misses", __func__, NULL);
517 if (hitfract > 1.0 || missfract > 1.0)
518 return (SEL *)ERROR_PTR("fraction can't be > 1.0", __func__, NULL);
519
520 if (distance <= 0)
521 distance = DefaultDistanceToBoundary;
522 if (distance > MaxDistanceToBoundary) {
523 L_WARNING("distance too large; setting to max value\n", __func__);
524 distance = MaxDistanceToBoundary;
525 }
526
527 /* Locate the foreground */
528 pixClipToForeground(pixs, &pixt1, NULL);
529 if (!pixt1)
530 return (SEL *)ERROR_PTR("pixt1 not made", __func__, NULL);
531 ws = pixGetWidth(pixt1);
532 hs = pixGetHeight(pixt1);
533 w = ws;
534 h = hs;
535
536 /* Crop out a region including the foreground, and add pixels
537 * on sides depending on the side flags */
538 if (toppix || botpix || leftpix || rightpix) {
539 x = y = 0;
540 if (toppix) {
541 h += toppix;
542 y = toppix;
543 }
544 if (botpix)
545 h += botpix;
546 if (leftpix) {
547 w += leftpix;
548 x = leftpix;
549 }
550 if (rightpix)
551 w += rightpix;
552 pixt2 = pixCreate(w, h, 1);
553 pixRasterop(pixt2, x, y, ws, hs, PIX_SRC, pixt1, 0, 0);
554 } else {
555 pixt2 = pixClone(pixt1);
556 }
557 if (ppixe)
558 *ppixe = pixClone(pixt2);
559 pixDestroy(&pixt1);
560
561 /* Identify fg and bg pixels that are at least 'distance' pixels
562 * away from the boundary pixels in their set */
563 seld = selCreateBrick(2 * distance + 1, 2 * distance + 1,
564 distance, distance, SEL_HIT);
565 pixfg = pixErode(NULL, pixt2, seld);
566 pixbg = pixDilate(NULL, pixt2, seld);
567 pixInvert(pixbg, pixbg);
568 selDestroy(&seld);
569 pixDestroy(&pixt2);
570
571 /* Generate the sel from a random selection of these points */
572 sel = selCreateBrick(h, w, h / 2, w / 2, SEL_DONT_CARE);
573 if (hitfract > 0.0) {
574 thresh = (l_int32)(hitfract * (l_float64)RAND_MAX);
575 for (i = 0; i < h; i++) {
576 for (j = 0; j < w; j++) {
577 pixGetPixel(pixfg, j, i, &val);
578 if (val) {
579 if (rand() < thresh)
580 selSetElement(sel, i, j, SEL_HIT);
581 }
582 }
583 }
584 }
585 if (missfract > 0.0) {
586 thresh = (l_int32)(missfract * (l_float64)RAND_MAX);
587 for (i = 0; i < h; i++) {
588 for (j = 0; j < w; j++) {
589 pixGetPixel(pixbg, j, i, &val);
590 if (val) {
591 if (rand() < thresh)
592 selSetElement(sel, i, j, SEL_MISS);
593 }
594 }
595 }
596 }
597
598 pixDestroy(&pixfg);
599 pixDestroy(&pixbg);
600 return sel;
601 }
602
603
604 /*-----------------------------------------------------------------*
605 * Accumulate data on runs along lines *
606 *-----------------------------------------------------------------*/
607 /*!
608 * \brief pixGetRunCentersOnLine()
609 *
610 * \param[in] pixs 1 bpp
611 * \param[in] x, y set one of these to -1; see notes
612 * \param[in] minlength minimum length of acceptable run
613 * \return numa of fg runs, or NULL on error
614 *
615 * <pre>
616 * Notes:
617 * (1) Action: this function computes the fg (black) and bg (white)
618 * pixel runlengths along the specified horizontal or vertical line,
619 * and returns a Numa of the "center" pixels of each fg run
620 * whose length equals or exceeds the minimum length.
621 * (2) This only works on horizontal and vertical lines.
622 * (3) For horizontal runs, set x = -1 and y to the value
623 * for all points along the raster line. For vertical runs,
624 * set y = -1 and x to the value for all points along the
625 * pixel column.
626 * (4) For horizontal runs, the points in the Numa are the x
627 * values in the center of fg runs that are of length at
628 * least 'minlength'. For vertical runs, the points in the
629 * Numa are the y values in the center of fg runs, again
630 * of length 'minlength' or greater.
631 * (5) If there are no fg runs along the line that satisfy the
632 * minlength constraint, the returned Numa is empty. This
633 * is not an error.
634 * </pre>
635 */
636 NUMA *
637 pixGetRunCentersOnLine(PIX *pixs,
638 l_int32 x,
639 l_int32 y,
640 l_int32 minlength)
641 {
642 l_int32 w, h, i, r, nruns, len;
643 NUMA *naruns, *nad;
644
645 if (!pixs)
646 return (NUMA *)ERROR_PTR("pixs not defined", __func__, NULL);
647 if (pixGetDepth(pixs) != 1)
648 return (NUMA *)ERROR_PTR("pixs not 1 bpp", __func__, NULL);
649 if (x != -1 && y != -1)
650 return (NUMA *)ERROR_PTR("x or y must be -1", __func__, NULL);
651 if (x == -1 && y == -1)
652 return (NUMA *)ERROR_PTR("x or y cannot both be -1", __func__, NULL);
653
654 if ((nad = numaCreate(0)) == NULL)
655 return (NUMA *)ERROR_PTR("nad not made", __func__, NULL);
656 w = pixGetWidth(pixs);
657 h = pixGetHeight(pixs);
658 if (x == -1) { /* horizontal run */
659 if (y < 0 || y >= h)
660 return nad;
661 naruns = pixGetRunsOnLine(pixs, 0, y, w - 1, y);
662 } else { /* vertical run */
663 if (x < 0 || x >= w)
664 return nad;
665 naruns = pixGetRunsOnLine(pixs, x, 0, x, h - 1);
666 }
667 nruns = numaGetCount(naruns);
668
669 /* extract run center values; the first run is always bg */
670 r = 0; /* cumulative distance along line */
671 for (i = 0; i < nruns; i++) {
672 if (i % 2 == 0) { /* bg run */
673 numaGetIValue(naruns, i, &len);
674 r += len;
675 continue;
676 } else {
677 numaGetIValue(naruns, i, &len);
678 if (len >= minlength)
679 numaAddNumber(nad, r + len / 2);
680 r += len;
681 }
682 }
683
684 numaDestroy(&naruns);
685 return nad;
686 }
687
688
689 /*!
690 * \brief pixGetRunsOnLine()
691 *
692 * \param[in] pixs 1 bpp
693 * \param[in] x1, y1, x2, y2
694 * \return numa, or NULL on error
695 *
696 * <pre>
697 * Notes:
698 * (1) Action: this function uses the bresenham algorithm to compute
699 * the pixels along the specified line. It returns a Numa of the
700 * runlengths of the fg (black) and bg (white) runs, always
701 * starting with a white run.
702 * (2) If the first pixel on the line is black, the length of the
703 * first returned run (which is white) is 0.
704 * </pre>
705 */
706 NUMA *
707 pixGetRunsOnLine(PIX *pixs,
708 l_int32 x1,
709 l_int32 y1,
710 l_int32 x2,
711 l_int32 y2)
712 {
713 l_int32 w, h, x, y, npts;
714 l_int32 i, runlen, preval;
715 l_uint32 val;
716 NUMA *numa;
717 PTA *pta;
718
719 if (!pixs)
720 return (NUMA *)ERROR_PTR("pixs not defined", __func__, NULL);
721 if (pixGetDepth(pixs) != 1)
722 return (NUMA *)ERROR_PTR("pixs not 1 bpp", __func__, NULL);
723
724 w = pixGetWidth(pixs);
725 h = pixGetHeight(pixs);
726 if (x1 < 0 || x1 >= w)
727 return (NUMA *)ERROR_PTR("x1 not valid", __func__, NULL);
728 if (x2 < 0 || x2 >= w)
729 return (NUMA *)ERROR_PTR("x2 not valid", __func__, NULL);
730 if (y1 < 0 || y1 >= h)
731 return (NUMA *)ERROR_PTR("y1 not valid", __func__, NULL);
732 if (y2 < 0 || y2 >= h)
733 return (NUMA *)ERROR_PTR("y2 not valid", __func__, NULL);
734
735 if ((pta = generatePtaLine(x1, y1, x2, y2)) == NULL)
736 return (NUMA *)ERROR_PTR("pta not made", __func__, NULL);
737 if ((npts = ptaGetCount(pta)) == 0) {
738 ptaDestroy(&pta);
739 return (NUMA *)ERROR_PTR("pta has no pts", __func__, NULL);
740 }
741 if ((numa = numaCreate(0)) == NULL) {
742 ptaDestroy(&pta);
743 return (NUMA *)ERROR_PTR("numa not made", __func__, NULL);
744 }
745
746 for (i = 0; i < npts; i++) {
747 ptaGetIPt(pta, i, &x, &y);
748 pixGetPixel(pixs, x, y, &val);
749 if (i == 0) {
750 if (val == 1) { /* black pixel; append white run of size 0 */
751 numaAddNumber(numa, 0);
752 }
753 preval = val;
754 runlen = 1;
755 continue;
756 }
757 if (val == preval) { /* extend current run */
758 preval = val;
759 runlen++;
760 } else { /* end previous run */
761 numaAddNumber(numa, runlen);
762 preval = val;
763 runlen = 1;
764 }
765 }
766 numaAddNumber(numa, runlen); /* append last run */
767
768 ptaDestroy(&pta);
769 return numa;
770 }
771
772
773 /*-----------------------------------------------------------------*
774 * Subsample boundary pixels in relatively ordered way *
775 *-----------------------------------------------------------------*/
776 /*!
777 * \brief pixSubsampleBoundaryPixels()
778 *
779 * \param[in] pixs 1 bpp, with only boundary pixels in fg
780 * \param[in] skip number to skip between samples as you traverse boundary
781 * \return pta, or NULL on error
782 *
783 * <pre>
784 * Notes:
785 * (1) If skip = 0, we take all the fg pixels.
786 * (2) We try to traverse the boundaries in a regular way.
787 * Some pixels may be missed, and these are then subsampled
788 * randomly with a fraction determined by 'skip'.
789 * (3) The most natural approach is to use a depth first (stack-based)
790 * method to find the fg pixels. However, the pixel runs are
791 * 4-connected and there are relatively few branches. So
792 * instead of doing a proper depth-first search, we get nearly
793 * the same result using two nested while loops: the outer
794 * one continues a raster-based search for the next fg pixel,
795 * and the inner one does a reasonable job running along
796 * each 4-connected coutour.
797 * </pre>
798 */
799 PTA *
800 pixSubsampleBoundaryPixels(PIX *pixs,
801 l_int32 skip)
802 {
803 l_int32 x, y, xn, yn, xs, ys, xa, ya, count;
804 PIX *pixt;
805 PTA *pta;
806
807 if (!pixs)
808 return (PTA *)ERROR_PTR("pixs not defined", __func__, NULL);
809 if (pixGetDepth(pixs) != 1)
810 return (PTA *)ERROR_PTR("pixs not 1 bpp", __func__, NULL);
811 if (skip < 0)
812 return (PTA *)ERROR_PTR("skip < 0", __func__, NULL);
813
814 if (skip == 0)
815 return ptaGetPixelsFromPix(pixs, NULL);
816
817 pta = ptaCreate(0);
818 pixt = pixCopy(NULL, pixs);
819 xs = ys = 0;
820 while (nextOnPixelInRaster(pixt, xs, ys, &xn, &yn)) { /* new series */
821 xs = xn;
822 ys = yn;
823
824 /* Add first point in this series */
825 ptaAddPt(pta, xs, ys);
826
827 /* Trace out boundary, erasing all and saving every (skip + 1)th */
828 x = xs;
829 y = ys;
830 pixSetPixel(pixt, x, y, 0);
831 count = 0;
832 while (adjacentOnPixelInRaster(pixt, x, y, &xa, &ya)) {
833 x = xa;
834 y = ya;
835 pixSetPixel(pixt, x, y, 0);
836 if (count == skip) {
837 ptaAddPt(pta, x, y);
838 count = 0;
839 } else {
840 count++;
841 }
842 }
843 }
844
845 pixDestroy(&pixt);
846 return pta;
847 }
848
849
850 /*!
851 * \brief adjacentOnPixelInRaster()
852 *
853 * \param[in] pixs 1 bpp
854 * \param[in] x, y current pixel
855 * \param[out] pxa, pya adjacent ON pixel, found by simple CCW search
856 * \return 1 if a pixel is found; 0 otherwise or on error
857 *
858 * <pre>
859 * Notes:
860 * (1) Search is in 4-connected directions first; then on diagonals.
861 * This allows traversal along a 4-connected boundary.
862 * </pre>
863 */
864 l_int32
865 adjacentOnPixelInRaster(PIX *pixs,
866 l_int32 x,
867 l_int32 y,
868 l_int32 *pxa,
869 l_int32 *pya)
870 {
871 l_int32 w, h, i, xa, ya, found;
872 l_int32 xdel[] = {-1, 0, 1, 0, -1, 1, 1, -1};
873 l_int32 ydel[] = {0, 1, 0, -1, 1, 1, -1, -1};
874 l_uint32 val;
875
876 if (!pixs)
877 return ERROR_INT("pixs not defined", __func__, 0);
878 if (pixGetDepth(pixs) != 1)
879 return ERROR_INT("pixs not 1 bpp", __func__, 0);
880 w = pixGetWidth(pixs);
881 h = pixGetHeight(pixs);
882 found = 0;
883 for (i = 0; i < 8; i++) {
884 xa = x + xdel[i];
885 ya = y + ydel[i];
886 if (xa < 0 || xa >= w || ya < 0 || ya >= h)
887 continue;
888 pixGetPixel(pixs, xa, ya, &val);
889 if (val == 1) {
890 found = 1;
891 *pxa = xa;
892 *pya = ya;
893 break;
894 }
895 }
896 return found;
897 }
898
899
900
901 /*-----------------------------------------------------------------*
902 * Display generated sel with originating image *
903 *-----------------------------------------------------------------*/
904 /*!
905 * \brief pixDisplayHitMissSel()
906 *
907 * \param[in] pixs 1 bpp
908 * \param[in] sel hit-miss in general
909 * \param[in] scalefactor an integer >= 1; use 0 for default
910 * \param[in] hitcolor RGB0 color for center of hit pixels
911 * \param[in] misscolor RGB0 color for center of miss pixels
912 * \return pixd RGB showing both pixs and sel, or NULL on error
913 * <pre>
914 * Notes:
915 * (1) We don't allow scalefactor to be larger than MaxSelScalefactor
916 * (2) The colors are conveniently given as 4 bytes in hex format,
917 * such as 0xff008800. The least significant byte is ignored.
918 * </pre>
919 */
920 PIX *
921 pixDisplayHitMissSel(PIX *pixs,
922 SEL *sel,
923 l_int32 scalefactor,
924 l_uint32 hitcolor,
925 l_uint32 misscolor)
926 {
927 l_int32 i, j, type;
928 l_float32 fscale;
929 PIX *pixt, *pixd;
930 PIXCMAP *cmap;
931
932 if (!pixs)
933 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
934 if (pixGetDepth(pixs) != 1)
935 return (PIX *)ERROR_PTR("pixs not 1 bpp", __func__, NULL);
936 if (!sel)
937 return (PIX *)ERROR_PTR("sel not defined", __func__, NULL);
938
939 if (scalefactor <= 0)
940 scalefactor = DefaultSelScalefactor;
941 if (scalefactor > MaxSelScalefactor) {
942 L_WARNING("scalefactor too large; using max value\n", __func__);
943 scalefactor = MaxSelScalefactor;
944 }
945
946 /* Generate a version of pixs with a colormap */
947 pixt = pixConvert1To8(NULL, pixs, 0, 1);
948 cmap = pixcmapCreate(8);
949 pixcmapAddColor(cmap, 255, 255, 255);
950 pixcmapAddColor(cmap, 0, 0, 0);
951 pixcmapAddColor(cmap, hitcolor >> 24, (hitcolor >> 16) & 0xff,
952 (hitcolor >> 8) & 0xff);
953 pixcmapAddColor(cmap, misscolor >> 24, (misscolor >> 16) & 0xff,
954 (misscolor >> 8) & 0xff);
955 pixSetColormap(pixt, cmap);
956
957 /* Color the hits and misses */
958 for (i = 0; i < sel->sy; i++) {
959 for (j = 0; j < sel->sx; j++) {
960 selGetElement(sel, i, j, &type);
961 if (type == SEL_DONT_CARE)
962 continue;
963 if (type == SEL_HIT)
964 pixSetPixel(pixt, j, i, 2);
965 else /* type == SEL_MISS */
966 pixSetPixel(pixt, j, i, 3);
967 }
968 }
969
970 /* Scale it up */
971 fscale = (l_float32)scalefactor;
972 pixd = pixScaleBySampling(pixt, fscale, fscale);
973
974 pixDestroy(&pixt);
975 return pixd;
976 }