comparison mupdf-source/thirdparty/leptonica/src/morphseq.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 morphseq.c
29 * <pre>
30 *
31 * Run a sequence of binary rasterop morphological operations
32 * PIX *pixMorphSequence()
33 *
34 * Run a sequence of binary composite rasterop morphological operations
35 * PIX *pixMorphCompSequence()
36 *
37 * Run a sequence of binary dwa morphological operations
38 * PIX *pixMorphSequenceDwa()
39 *
40 * Run a sequence of binary composite dwa morphological operations
41 * PIX *pixMorphCompSequenceDwa()
42 *
43 * Parser verifier for binary morphological operations
44 * l_int32 morphSequenceVerify()
45 *
46 * Run a sequence of grayscale morphological operations
47 * PIX *pixGrayMorphSequence()
48 *
49 * Run a sequence of color morphological operations
50 * PIX *pixColorMorphSequence()
51 * </pre>
52 */
53
54 #ifdef HAVE_CONFIG_H
55 #include <config_auto.h>
56 #endif /* HAVE_CONFIG_H */
57
58 #include <string.h>
59 #include "allheaders.h"
60
61 /*-------------------------------------------------------------------------*
62 * Run a sequence of binary rasterop morphological operations *
63 *-------------------------------------------------------------------------*/
64 /*!
65 * \brief pixMorphSequence()
66 *
67 * \param[in] pixs
68 * \param[in] sequence string specifying sequence
69 * \param[in] dispsep controls debug display results in the sequence:
70 * 0: no output
71 * > 0: gives horizontal separation in pixels between
72 * successive displays
73 * < 0: pdf output; abs(dispsep) is used for naming
74 * \return pixd, or NULL on error
75 *
76 * <pre>
77 * Notes:
78 * (1) This does rasterop morphology on binary images.
79 * (2) This runs a pipeline of operations; no branching is allowed.
80 * (3) This only uses brick Sels, which are created on the fly.
81 * In the future this will be generalized to extract Sels from
82 * a Sela by name.
83 * (4) A new image is always produced; the input image is not changed.
84 * (5) This contains an interpreter, allowing sequences to be
85 * generated and run.
86 * (6) The format of the sequence string is defined below.
87 * (7) In addition to morphological operations, rank order reduction
88 * and replicated expansion allow operations to take place
89 * downscaled by a power of 2.
90 * (8) Intermediate results can optionally be displayed.
91 * (9) Thanks to Dar-Shyang Lee, who had the idea for this and
92 * built the first implementation.
93 * (10) The sequence string is formatted as follows:
94 * ~ An arbitrary number of operations, each separated
95 * by a '+' character. White space is ignored.
96 * ~ Each operation begins with a case-independent character
97 * specifying the operation:
98 * d or D (dilation)
99 * e or E (erosion)
100 * o or O (opening)
101 * c or C (closing)
102 * r or R (rank binary reduction)
103 * x or X (replicative binary expansion)
104 * b or B (add a border of 0 pixels of this size)
105 * ~ The args to the morphological operations are bricks of hits,
106 * and are formatted as a.b, where a and b are horizontal and
107 * vertical dimensions, rsp.
108 * ~ The args to the reduction are a sequence of up to 4 integers,
109 * each from 1 to 4.
110 * ~ The arg to the expansion is a power of two, in the set
111 * {2, 4, 8, 16}.
112 * (11) An example valid sequence is:
113 * "b32 + o1.3 + C3.1 + r23 + e2.2 + D3.2 + X4"
114 * In this example, the following operation sequence is carried out:
115 * * b32: Add a 32 pixel border around the input image
116 * * o1.3: Opening with vert sel of length 3 (e.g., 1 x 3)
117 * * C3.1: Closing with horiz sel of length 3 (e.g., 3 x 1)
118 * * r23: Two successive 2x2 reductions with rank 2 in the first
119 * and rank 3 in the second. The result is a 4x reduced pix.
120 * * e2.2: Erosion with a 2x2 sel (origin will be at x,y: 0,0)
121 * * d3.2: Dilation with a 3x2 sel (origin will be at x,y: 1,0)
122 * * X4: 4x replicative expansion, back to original resolution
123 * (12) The safe closing is used. However, if you implement a
124 * closing as separable dilations followed by separable erosions,
125 * it will not be safe. For that situation, you need to add
126 * a sufficiently large border as the first operation in
127 * the sequence. This will be removed automatically at the
128 * end. There are two cautions:
129 * ~ When computing what is sufficient, remember that if
130 * reductions are carried out, the border is also reduced.
131 * ~ The border is removed at the end, so if a border is
132 * added at the beginning, the result must be at the
133 * same resolution as the input!
134 * </pre>
135 */
136 PIX *
137 pixMorphSequence(PIX *pixs,
138 const char *sequence,
139 l_int32 dispsep)
140 {
141 char *rawop, *op;
142 char fname[256];
143 l_int32 nops, i, j, nred, fact, w, h, x, border, pdfout;
144 l_int32 level[4];
145 PIX *pix1, *pix2;
146 PIXA *pixa;
147 SARRAY *sa;
148
149 if (!pixs)
150 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
151 if (!sequence)
152 return (PIX *)ERROR_PTR("sequence not defined", __func__, NULL);
153
154 /* Split sequence into individual operations */
155 sa = sarrayCreate(0);
156 sarraySplitString(sa, sequence, "+");
157 nops = sarrayGetCount(sa);
158 pdfout = (dispsep < 0) ? 1 : 0;
159 if (!morphSequenceVerify(sa)) {
160 sarrayDestroy(&sa);
161 return (PIX *)ERROR_PTR("sequence not valid", __func__, NULL);
162 }
163
164 /* Parse and operate */
165 pixa = NULL;
166 if (pdfout) {
167 pixa = pixaCreate(0);
168 pixaAddPix(pixa, pixs, L_CLONE);
169 }
170 border = 0;
171 pix1 = pixCopy(NULL, pixs);
172 pix2 = NULL;
173 x = 0;
174 for (i = 0; i < nops; i++) {
175 rawop = sarrayGetString(sa, i, L_NOCOPY);
176 op = stringRemoveChars(rawop, " \n\t");
177 switch (op[0])
178 {
179 case 'd':
180 case 'D':
181 sscanf(&op[1], "%d.%d", &w, &h);
182 pix2 = pixDilateBrick(NULL, pix1, w, h);
183 pixSwapAndDestroy(&pix1, &pix2);
184 break;
185 case 'e':
186 case 'E':
187 sscanf(&op[1], "%d.%d", &w, &h);
188 pix2 = pixErodeBrick(NULL, pix1, w, h);
189 pixSwapAndDestroy(&pix1, &pix2);
190 break;
191 case 'o':
192 case 'O':
193 sscanf(&op[1], "%d.%d", &w, &h);
194 pixOpenBrick(pix1, pix1, w, h);
195 break;
196 case 'c':
197 case 'C':
198 sscanf(&op[1], "%d.%d", &w, &h);
199 pixCloseSafeBrick(pix1, pix1, w, h);
200 break;
201 case 'r':
202 case 'R':
203 nred = strlen(op) - 1;
204 for (j = 0; j < nred; j++)
205 level[j] = op[j + 1] - '0';
206 for (j = nred; j < 4; j++)
207 level[j] = 0;
208 pix2 = pixReduceRankBinaryCascade(pix1, level[0], level[1],
209 level[2], level[3]);
210 pixSwapAndDestroy(&pix1, &pix2);
211 break;
212 case 'x':
213 case 'X':
214 sscanf(&op[1], "%d", &fact);
215 pix2 = pixExpandReplicate(pix1, fact);
216 pixSwapAndDestroy(&pix1, &pix2);
217 break;
218 case 'b':
219 case 'B':
220 sscanf(&op[1], "%d", &border);
221 pix2 = pixAddBorder(pix1, border, 0);
222 pixSwapAndDestroy(&pix1, &pix2);
223 break;
224 default:
225 /* All invalid ops are caught in the first pass */
226 break;
227 }
228 LEPT_FREE(op);
229
230 /* Debug output */
231 if (dispsep > 0) {
232 pixDisplay(pix1, x, 0);
233 x += dispsep;
234 }
235 if (pdfout)
236 pixaAddPix(pixa, pix1, L_COPY);
237 }
238 if (border > 0) {
239 pix2 = pixRemoveBorder(pix1, border);
240 pixSwapAndDestroy(&pix1, &pix2);
241 }
242
243 if (pdfout) {
244 snprintf(fname, sizeof(fname), "/tmp/lept/seq_output_%d.pdf",
245 L_ABS(dispsep));
246 pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname);
247 pixaDestroy(&pixa);
248 }
249
250 sarrayDestroy(&sa);
251 return pix1;
252 }
253
254
255 /*-------------------------------------------------------------------------*
256 * Run a sequence of binary composite rasterop morphological operations *
257 *-------------------------------------------------------------------------*/
258 /*!
259 * \brief pixMorphCompSequence()
260 *
261 * \param[in] pixs
262 * \param[in] sequence string specifying sequence
263 * \param[in] dispsep controls debug display of results in the sequence:
264 * 0: no output
265 * > 0: gives horizontal separation in pixels between
266 * successive displays
267 * < 0: pdf output; abs(dispsep) is used for naming
268 * \return pixd, or NULL on error
269 *
270 * <pre>
271 * Notes:
272 * (1) This does rasterop morphology on binary images, using composite
273 * operations for extra speed on large Sels.
274 * (2) Safe closing is used atomically. However, if you implement a
275 * closing as a sequence with a dilation followed by an
276 * erosion, it will not be safe, and to ensure that you have
277 * no boundary effects you must add a border in advance and
278 * remove it at the end.
279 * (3) For other usage details, see the notes for pixMorphSequence().
280 * (4) The sequence string is formatted as follows:
281 * ~ An arbitrary number of operations, each separated
282 * by a '+' character. White space is ignored.
283 * ~ Each operation begins with a case-independent character
284 * specifying the operation:
285 * d or D (dilation)
286 * e or E (erosion)
287 * o or O (opening)
288 * c or C (closing)
289 * r or R (rank binary reduction)
290 * x or X (replicative binary expansion)
291 * b or B (add a border of 0 pixels of this size)
292 * ~ The args to the morphological operations are bricks of hits,
293 * and are formatted as a.b, where a and b are horizontal and
294 * vertical dimensions, rsp.
295 * ~ The args to the reduction are a sequence of up to 4 integers,
296 * each from 1 to 4.
297 * ~ The arg to the expansion is a power of two, in the set
298 * {2, 4, 8, 16}.
299 * </pre>
300 */
301 PIX *
302 pixMorphCompSequence(PIX *pixs,
303 const char *sequence,
304 l_int32 dispsep)
305 {
306 char *rawop, *op;
307 char fname[256];
308 l_int32 nops, i, j, nred, fact, w, h, x, border, pdfout;
309 l_int32 level[4];
310 PIX *pix1, *pix2;
311 PIXA *pixa;
312 SARRAY *sa;
313
314 if (!pixs)
315 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
316 if (!sequence)
317 return (PIX *)ERROR_PTR("sequence not defined", __func__, NULL);
318
319 /* Split sequence into individual operations */
320 sa = sarrayCreate(0);
321 sarraySplitString(sa, sequence, "+");
322 nops = sarrayGetCount(sa);
323 pdfout = (dispsep < 0) ? 1 : 0;
324
325 if (!morphSequenceVerify(sa)) {
326 sarrayDestroy(&sa);
327 return (PIX *)ERROR_PTR("sequence not valid", __func__, NULL);
328 }
329
330 /* Parse and operate */
331 pixa = NULL;
332 if (pdfout) {
333 pixa = pixaCreate(0);
334 pixaAddPix(pixa, pixs, L_CLONE);
335 }
336 border = 0;
337 pix1 = pixCopy(NULL, pixs);
338 pix2 = NULL;
339 x = 0;
340 for (i = 0; i < nops; i++) {
341 rawop = sarrayGetString(sa, i, L_NOCOPY);
342 op = stringRemoveChars(rawop, " \n\t");
343 switch (op[0])
344 {
345 case 'd':
346 case 'D':
347 sscanf(&op[1], "%d.%d", &w, &h);
348 pix2 = pixDilateCompBrick(NULL, pix1, w, h);
349 pixSwapAndDestroy(&pix1, &pix2);
350 break;
351 case 'e':
352 case 'E':
353 sscanf(&op[1], "%d.%d", &w, &h);
354 pix2 = pixErodeCompBrick(NULL, pix1, w, h);
355 pixSwapAndDestroy(&pix1, &pix2);
356 break;
357 case 'o':
358 case 'O':
359 sscanf(&op[1], "%d.%d", &w, &h);
360 pixOpenCompBrick(pix1, pix1, w, h);
361 break;
362 case 'c':
363 case 'C':
364 sscanf(&op[1], "%d.%d", &w, &h);
365 pixCloseSafeCompBrick(pix1, pix1, w, h);
366 break;
367 case 'r':
368 case 'R':
369 nred = strlen(op) - 1;
370 for (j = 0; j < nred; j++)
371 level[j] = op[j + 1] - '0';
372 for (j = nred; j < 4; j++)
373 level[j] = 0;
374 pix2 = pixReduceRankBinaryCascade(pix1, level[0], level[1],
375 level[2], level[3]);
376 pixSwapAndDestroy(&pix1, &pix2);
377 break;
378 case 'x':
379 case 'X':
380 sscanf(&op[1], "%d", &fact);
381 pix2 = pixExpandReplicate(pix1, fact);
382 pixSwapAndDestroy(&pix1, &pix2);
383 break;
384 case 'b':
385 case 'B':
386 sscanf(&op[1], "%d", &border);
387 pix2 = pixAddBorder(pix1, border, 0);
388 pixSwapAndDestroy(&pix1, &pix2);
389 break;
390 default:
391 /* All invalid ops are caught in the first pass */
392 break;
393 }
394 LEPT_FREE(op);
395
396 /* Debug output */
397 if (dispsep > 0) {
398 pixDisplay(pix1, x, 0);
399 x += dispsep;
400 }
401 if (pdfout)
402 pixaAddPix(pixa, pix1, L_COPY);
403 }
404 if (border > 0) {
405 pix2 = pixRemoveBorder(pix1, border);
406 pixSwapAndDestroy(&pix1, &pix2);
407 }
408
409 if (pdfout) {
410 snprintf(fname, sizeof(fname), "/tmp/lept/seq_output_%d.pdf",
411 L_ABS(dispsep));
412 pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname);
413 pixaDestroy(&pixa);
414 }
415
416 sarrayDestroy(&sa);
417 return pix1;
418 }
419
420
421 /*-------------------------------------------------------------------------*
422 * Run a sequence of binary dwa morphological operations *
423 *-------------------------------------------------------------------------*/
424 /*!
425 * \brief pixMorphSequenceDwa()
426 *
427 * \param[in] pixs
428 * \param[in] sequence string specifying sequence
429 * \param[in] dispsep controls debug display of results in the sequence:
430 * 0: no output
431 * > 0: gives horizontal separation in pixels between
432 * successive displays
433 * < 0: pdf output; abs(dispsep) is used for naming
434 * \return pixd, or NULL on error
435 *
436 * <pre>
437 * Notes:
438 * (1) This does dwa morphology on binary images.
439 * (2) This runs a pipeline of operations; no branching is allowed.
440 * (3) This only uses brick Sels that have been pre-compiled with
441 * dwa code.
442 * (4) A new image is always produced; the input image is not changed.
443 * (5) This contains an interpreter, allowing sequences to be
444 * generated and run.
445 * (6) See pixMorphSequence() for further information about usage.
446 * </pre>
447 */
448 PIX *
449 pixMorphSequenceDwa(PIX *pixs,
450 const char *sequence,
451 l_int32 dispsep)
452 {
453 char *rawop, *op;
454 char fname[256];
455 l_int32 nops, i, j, nred, fact, w, h, x, border, pdfout;
456 l_int32 level[4];
457 PIX *pix1, *pix2;
458 PIXA *pixa;
459 SARRAY *sa;
460
461 if (!pixs)
462 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
463 if (!sequence)
464 return (PIX *)ERROR_PTR("sequence not defined", __func__, NULL);
465
466 /* Split sequence into individual operations */
467 sa = sarrayCreate(0);
468 sarraySplitString(sa, sequence, "+");
469 nops = sarrayGetCount(sa);
470 pdfout = (dispsep < 0) ? 1 : 0;
471
472 if (!morphSequenceVerify(sa)) {
473 sarrayDestroy(&sa);
474 return (PIX *)ERROR_PTR("sequence not valid", __func__, NULL);
475 }
476
477 /* Parse and operate */
478 pixa = NULL;
479 if (pdfout) {
480 pixa = pixaCreate(0);
481 pixaAddPix(pixa, pixs, L_CLONE);
482 }
483 border = 0;
484 pix1 = pixCopy(NULL, pixs);
485 pix2 = NULL;
486 x = 0;
487 for (i = 0; i < nops; i++) {
488 rawop = sarrayGetString(sa, i, L_NOCOPY);
489 op = stringRemoveChars(rawop, " \n\t");
490 switch (op[0])
491 {
492 case 'd':
493 case 'D':
494 sscanf(&op[1], "%d.%d", &w, &h);
495 pix2 = pixDilateBrickDwa(NULL, pix1, w, h);
496 pixSwapAndDestroy(&pix1, &pix2);
497 break;
498 case 'e':
499 case 'E':
500 sscanf(&op[1], "%d.%d", &w, &h);
501 pix2 = pixErodeBrickDwa(NULL, pix1, w, h);
502 pixSwapAndDestroy(&pix1, &pix2);
503 break;
504 case 'o':
505 case 'O':
506 sscanf(&op[1], "%d.%d", &w, &h);
507 pixOpenBrickDwa(pix1, pix1, w, h);
508 break;
509 case 'c':
510 case 'C':
511 sscanf(&op[1], "%d.%d", &w, &h);
512 pixCloseBrickDwa(pix1, pix1, w, h);
513 break;
514 case 'r':
515 case 'R':
516 nred = strlen(op) - 1;
517 for (j = 0; j < nred; j++)
518 level[j] = op[j + 1] - '0';
519 for (j = nred; j < 4; j++)
520 level[j] = 0;
521 pix2 = pixReduceRankBinaryCascade(pix1, level[0], level[1],
522 level[2], level[3]);
523 pixSwapAndDestroy(&pix1, &pix2);
524 break;
525 case 'x':
526 case 'X':
527 sscanf(&op[1], "%d", &fact);
528 pix2 = pixExpandReplicate(pix1, fact);
529 pixSwapAndDestroy(&pix1, &pix2);
530 break;
531 case 'b':
532 case 'B':
533 sscanf(&op[1], "%d", &border);
534 pix2 = pixAddBorder(pix1, border, 0);
535 pixSwapAndDestroy(&pix1, &pix2);
536 break;
537 default:
538 /* All invalid ops are caught in the first pass */
539 break;
540 }
541 LEPT_FREE(op);
542
543 /* Debug output */
544 if (dispsep > 0) {
545 pixDisplay(pix1, x, 0);
546 x += dispsep;
547 }
548 if (pdfout)
549 pixaAddPix(pixa, pix1, L_COPY);
550 }
551 if (border > 0) {
552 pix2 = pixRemoveBorder(pix1, border);
553 pixSwapAndDestroy(&pix1, &pix2);
554 }
555
556 if (pdfout) {
557 snprintf(fname, sizeof(fname), "/tmp/lept/seq_output_%d.pdf",
558 L_ABS(dispsep));
559 pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname);
560 pixaDestroy(&pixa);
561 }
562
563 sarrayDestroy(&sa);
564 return pix1;
565 }
566
567
568 /*-------------------------------------------------------------------------*
569 * Run a sequence of binary composite dwa morphological operations *
570 *-------------------------------------------------------------------------*/
571 /*!
572 * \brief pixMorphCompSequenceDwa()
573 *
574 * \param[in] pixs
575 * \param[in] sequence string specifying sequence
576 * \param[in] dispsep controls debug display of results in the sequence:
577 * 0: no output
578 * > 0: gives horizontal separation in pixels between
579 * successive displays
580 * < 0: pdf output; abs(dispsep) is used for naming
581 * \return pixd, or NULL on error
582 *
583 * <pre>
584 * Notes:
585 * (1) This does dwa morphology on binary images, using brick Sels.
586 * (2) This runs a pipeline of operations; no branching is allowed.
587 * (3) It implements all brick Sels that have dimensions up to 63
588 * on each side, using a composite (linear + comb) when useful.
589 * (4) A new image is always produced; the input image is not changed.
590 * (5) This contains an interpreter, allowing sequences to be
591 * generated and run.
592 * (6) See pixMorphSequence() for further information about usage.
593 * </pre>
594 */
595 PIX *
596 pixMorphCompSequenceDwa(PIX *pixs,
597 const char *sequence,
598 l_int32 dispsep)
599 {
600 char *rawop, *op;
601 char fname[256];
602 l_int32 nops, i, j, nred, fact, w, h, x, border, pdfout;
603 l_int32 level[4];
604 PIX *pix1, *pix2;
605 PIXA *pixa;
606 SARRAY *sa;
607
608 if (!pixs)
609 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
610 if (!sequence)
611 return (PIX *)ERROR_PTR("sequence not defined", __func__, NULL);
612
613 /* Split sequence into individual operations */
614 sa = sarrayCreate(0);
615 sarraySplitString(sa, sequence, "+");
616 nops = sarrayGetCount(sa);
617 pdfout = (dispsep < 0) ? 1 : 0;
618
619 if (!morphSequenceVerify(sa)) {
620 sarrayDestroy(&sa);
621 return (PIX *)ERROR_PTR("sequence not valid", __func__, NULL);
622 }
623
624 /* Parse and operate */
625 pixa = NULL;
626 if (pdfout) {
627 pixa = pixaCreate(0);
628 pixaAddPix(pixa, pixs, L_CLONE);
629 }
630 border = 0;
631 pix1 = pixCopy(NULL, pixs);
632 pix2 = NULL;
633 x = 0;
634 for (i = 0; i < nops; i++) {
635 rawop = sarrayGetString(sa, i, L_NOCOPY);
636 op = stringRemoveChars(rawop, " \n\t");
637 switch (op[0])
638 {
639 case 'd':
640 case 'D':
641 sscanf(&op[1], "%d.%d", &w, &h);
642 pix2 = pixDilateCompBrickDwa(NULL, pix1, w, h);
643 pixSwapAndDestroy(&pix1, &pix2);
644 break;
645 case 'e':
646 case 'E':
647 sscanf(&op[1], "%d.%d", &w, &h);
648 pix2 = pixErodeCompBrickDwa(NULL, pix1, w, h);
649 pixSwapAndDestroy(&pix1, &pix2);
650 break;
651 case 'o':
652 case 'O':
653 sscanf(&op[1], "%d.%d", &w, &h);
654 pixOpenCompBrickDwa(pix1, pix1, w, h);
655 break;
656 case 'c':
657 case 'C':
658 sscanf(&op[1], "%d.%d", &w, &h);
659 pixCloseCompBrickDwa(pix1, pix1, w, h);
660 break;
661 case 'r':
662 case 'R':
663 nred = strlen(op) - 1;
664 for (j = 0; j < nred; j++)
665 level[j] = op[j + 1] - '0';
666 for (j = nred; j < 4; j++)
667 level[j] = 0;
668 pix2 = pixReduceRankBinaryCascade(pix1, level[0], level[1],
669 level[2], level[3]);
670 pixSwapAndDestroy(&pix1, &pix2);
671 break;
672 case 'x':
673 case 'X':
674 sscanf(&op[1], "%d", &fact);
675 pix2 = pixExpandReplicate(pix1, fact);
676 pixSwapAndDestroy(&pix1, &pix2);
677 break;
678 case 'b':
679 case 'B':
680 sscanf(&op[1], "%d", &border);
681 pix2 = pixAddBorder(pix1, border, 0);
682 pixSwapAndDestroy(&pix1, &pix2);
683 break;
684 default:
685 /* All invalid ops are caught in the first pass */
686 break;
687 }
688 LEPT_FREE(op);
689
690 /* Debug output */
691 if (dispsep > 0) {
692 pixDisplay(pix1, x, 0);
693 x += dispsep;
694 }
695 if (pdfout)
696 pixaAddPix(pixa, pix1, L_COPY);
697 }
698 if (border > 0) {
699 pix2 = pixRemoveBorder(pix1, border);
700 pixSwapAndDestroy(&pix1, &pix2);
701 }
702
703 if (pdfout) {
704 snprintf(fname, sizeof(fname), "/tmp/lept/seq_output_%d.pdf",
705 L_ABS(dispsep));
706 pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname);
707 pixaDestroy(&pixa);
708 }
709
710 sarrayDestroy(&sa);
711 return pix1;
712 }
713
714
715 /*-------------------------------------------------------------------------*
716 * Parser verifier for binary morphological operations *
717 *-------------------------------------------------------------------------*/
718 /*!
719 * \brief morphSequenceVerify()
720 *
721 * \param[in] sa string array of operation sequence
722 * \return TRUE if valid; FALSE otherwise or on error
723 *
724 * <pre>
725 * Notes:
726 * (1) This does verification of valid binary morphological
727 * operation sequences.
728 * (2) See pixMorphSequence() for notes on valid operations
729 * in the sequence.
730 * </pre>
731 */
732 l_int32
733 morphSequenceVerify(SARRAY *sa)
734 {
735 char *rawop, *op = NULL;
736 l_int32 nops, i, j, nred, fact, valid, w, h, netred, border;
737 l_int32 level[4];
738 l_int32 intlogbase2[5] = {1, 2, 3, 0, 4}; /* of arg/4 */
739
740 if (!sa)
741 return ERROR_INT("sa not defined", __func__, FALSE);
742
743 nops = sarrayGetCount(sa);
744 valid = TRUE;
745 netred = 0;
746 border = 0;
747 for (i = 0; i < nops; i++) {
748 rawop = sarrayGetString(sa, i, L_NOCOPY);
749 op = stringRemoveChars(rawop, " \n\t");
750 switch (op[0])
751 {
752 case 'd':
753 case 'D':
754 case 'e':
755 case 'E':
756 case 'o':
757 case 'O':
758 case 'c':
759 case 'C':
760 if (sscanf(&op[1], "%d.%d", &w, &h) != 2) {
761 lept_stderr("*** op: %s invalid\n", op);
762 valid = FALSE;
763 break;
764 }
765 if (w <= 0 || h <= 0) {
766 lept_stderr("*** op: %s; w = %d, h = %d; must both be > 0\n",
767 op, w, h);
768 valid = FALSE;
769 break;
770 }
771 /* lept_stderr("op = %s; w = %d, h = %d\n", op, w, h); */
772 break;
773 case 'r':
774 case 'R':
775 nred = strlen(op) - 1;
776 netred += nred;
777 if (nred < 1 || nred > 4) {
778 lept_stderr(
779 "*** op = %s; num reduct = %d; must be in {1,2,3,4}\n",
780 op, nred);
781 valid = FALSE;
782 break;
783 }
784 for (j = 0; j < nred; j++) {
785 level[j] = op[j + 1] - '0';
786 if (level[j] < 1 || level[j] > 4) {
787 lept_stderr("*** op = %s; level[%d] = %d is invalid\n",
788 op, j, level[j]);
789 valid = FALSE;
790 break;
791 }
792 }
793 if (!valid)
794 break;
795 /* lept_stderr("op = %s", op); */
796 for (j = 0; j < nred; j++) {
797 level[j] = op[j + 1] - '0';
798 /* lept_stderr(", level[%d] = %d", j, level[j]); */
799 }
800 /* lept_stderr("\n"); */
801 break;
802 case 'x':
803 case 'X':
804 if (sscanf(&op[1], "%d", &fact) != 1) {
805 lept_stderr("*** op: %s; fact invalid\n", op);
806 valid = FALSE;
807 break;
808 }
809 if (fact != 2 && fact != 4 && fact != 8 && fact != 16) {
810 lept_stderr("*** op = %s; invalid fact = %d\n", op, fact);
811 valid = FALSE;
812 break;
813 }
814 netred -= intlogbase2[fact / 4];
815 /* lept_stderr("op = %s; fact = %d\n", op, fact); */
816 break;
817 case 'b':
818 case 'B':
819 if (sscanf(&op[1], "%d", &fact) != 1) {
820 lept_stderr("*** op: %s; fact invalid\n", op);
821 valid = FALSE;
822 break;
823 }
824 if (i > 0) {
825 lept_stderr("*** op = %s; must be first op\n", op);
826 valid = FALSE;
827 break;
828 }
829 if (fact < 1) {
830 lept_stderr("*** op = %s; invalid fact = %d\n", op, fact);
831 valid = FALSE;
832 break;
833 }
834 border = fact;
835 /* lept_stderr("op = %s; fact = %d\n", op, fact); */
836 break;
837 default:
838 lept_stderr("*** nonexistent op = %s\n", op);
839 valid = FALSE;
840 }
841 LEPT_FREE(op);
842 }
843
844 if (border != 0 && netred != 0) {
845 lept_stderr("*** op = %s; border added but net reduction not 0\n", op);
846 valid = FALSE;
847 }
848 return valid;
849 }
850
851
852 /*-----------------------------------------------------------------*
853 * Run a sequence of grayscale morphological operations *
854 *-----------------------------------------------------------------*/
855 /*!
856 * \brief pixGrayMorphSequence()
857 *
858 * \param[in] pixs
859 * \param[in] sequence string specifying sequence
860 * \param[in] dispsep controls debug display of results in the sequence:
861 * 0: no output
862 * > 0: gives horizontal separation in pixels between
863 * successive displays
864 * < 0: pdf output; abs(dispsep) is used for naming
865 * \param[in] dispy if dispsep > 0, this gives the y-value of the
866 * UL corner for display; otherwise it is ignored
867 * \return pixd, or NULL on error
868 *
869 * <pre>
870 * Notes:
871 * (1) This works on 8 bpp grayscale images.
872 * (2) This runs a pipeline of operations; no branching is allowed.
873 * (3) This only uses brick SELs.
874 * (4) A new image is always produced; the input image is not changed.
875 * (5) This contains an interpreter, allowing sequences to be
876 * generated and run.
877 * (6) The format of the sequence string is defined below.
878 * (7) In addition to morphological operations, the composite
879 * morph/subtract tophat can be performed.
880 * (8) Sel sizes (width, height) must each be odd numbers.
881 * (9) Intermediate results can optionally be displayed
882 * (10) The sequence string is formatted as follows:
883 * ~ An arbitrary number of operations, each separated
884 * by a '+' character. White space is ignored.
885 * ~ Each operation begins with a case-independent character
886 * specifying the operation:
887 * d or D (dilation)
888 * e or E (erosion)
889 * o or O (opening)
890 * c or C (closing)
891 * t or T (tophat)
892 * ~ The args to the morphological operations are bricks of hits,
893 * and are formatted as a.b, where a and b are horizontal and
894 * vertical dimensions, rsp. (each must be an odd number)
895 * ~ The args to the tophat are w or W (for white tophat)
896 * or b or B (for black tophat), followed by a.b as for
897 * the dilation, erosion, opening and closing.
898 * Example valid sequences are:
899 * "c5.3 + o7.5"
900 * "c9.9 + tw9.9"
901 * </pre>
902 */
903 PIX *
904 pixGrayMorphSequence(PIX *pixs,
905 const char *sequence,
906 l_int32 dispsep,
907 l_int32 dispy)
908 {
909 char *rawop, *op;
910 char fname[256];
911 l_int32 nops, i, valid, w, h, x, pdfout;
912 PIX *pix1, *pix2;
913 PIXA *pixa;
914 SARRAY *sa;
915
916 if (!pixs)
917 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
918 if (!sequence)
919 return (PIX *)ERROR_PTR("sequence not defined", __func__, NULL);
920
921 /* Split sequence into individual operations */
922 sa = sarrayCreate(0);
923 sarraySplitString(sa, sequence, "+");
924 nops = sarrayGetCount(sa);
925 pdfout = (dispsep < 0) ? 1 : 0;
926
927 /* Verify that the operation sequence is valid */
928 valid = TRUE;
929 for (i = 0; i < nops; i++) {
930 rawop = sarrayGetString(sa, i, L_NOCOPY);
931 op = stringRemoveChars(rawop, " \n\t");
932 switch (op[0])
933 {
934 case 'd':
935 case 'D':
936 case 'e':
937 case 'E':
938 case 'o':
939 case 'O':
940 case 'c':
941 case 'C':
942 if (sscanf(&op[1], "%d.%d", &w, &h) != 2) {
943 lept_stderr("*** op: %s invalid\n", op);
944 valid = FALSE;
945 break;
946 }
947 if (w < 1 || (w & 1) == 0 || h < 1 || (h & 1) == 0 ) {
948 lept_stderr("*** op: %s; w = %d, h = %d; must both be odd\n",
949 op, w, h);
950 valid = FALSE;
951 break;
952 }
953 /* lept_stderr("op = %s; w = %d, h = %d\n", op, w, h); */
954 break;
955 case 't':
956 case 'T':
957 if (op[1] != 'w' && op[1] != 'W' &&
958 op[1] != 'b' && op[1] != 'B') {
959 lept_stderr(
960 "*** op = %s; arg %c must be 'w' or 'b'\n", op, op[1]);
961 valid = FALSE;
962 break;
963 }
964 sscanf(&op[2], "%d.%d", &w, &h);
965 if (w < 1 || (w & 1) == 0 || h < 1 || (h & 1) == 0 ) {
966 lept_stderr("*** op: %s; w = %d, h = %d; must both be odd\n",
967 op, w, h);
968 valid = FALSE;
969 break;
970 }
971 /* lept_stderr("op = %s", op); */
972 break;
973 default:
974 lept_stderr("*** nonexistent op = %s\n", op);
975 valid = FALSE;
976 }
977 LEPT_FREE(op);
978 }
979 if (!valid) {
980 sarrayDestroy(&sa);
981 return (PIX *)ERROR_PTR("sequence invalid", __func__, NULL);
982 }
983
984 /* Parse and operate */
985 pixa = NULL;
986 if (pdfout) {
987 pixa = pixaCreate(0);
988 pixaAddPix(pixa, pixs, L_CLONE);
989 }
990 pix1 = pixCopy(NULL, pixs);
991 pix2 = NULL;
992 x = 0;
993 for (i = 0; i < nops; i++) {
994 rawop = sarrayGetString(sa, i, L_NOCOPY);
995 op = stringRemoveChars(rawop, " \n\t");
996 switch (op[0])
997 {
998 case 'd':
999 case 'D':
1000 sscanf(&op[1], "%d.%d", &w, &h);
1001 pix2 = pixDilateGray(pix1, w, h);
1002 pixSwapAndDestroy(&pix1, &pix2);
1003 break;
1004 case 'e':
1005 case 'E':
1006 sscanf(&op[1], "%d.%d", &w, &h);
1007 pix2 = pixErodeGray(pix1, w, h);
1008 pixSwapAndDestroy(&pix1, &pix2);
1009 break;
1010 case 'o':
1011 case 'O':
1012 sscanf(&op[1], "%d.%d", &w, &h);
1013 pix2 = pixOpenGray(pix1, w, h);
1014 pixSwapAndDestroy(&pix1, &pix2);
1015 break;
1016 case 'c':
1017 case 'C':
1018 sscanf(&op[1], "%d.%d", &w, &h);
1019 pix2 = pixCloseGray(pix1, w, h);
1020 pixSwapAndDestroy(&pix1, &pix2);
1021 break;
1022 case 't':
1023 case 'T':
1024 sscanf(&op[2], "%d.%d", &w, &h);
1025 if (op[1] == 'w' || op[1] == 'W')
1026 pix2 = pixTophat(pix1, w, h, L_TOPHAT_WHITE);
1027 else /* 'b' or 'B' */
1028 pix2 = pixTophat(pix1, w, h, L_TOPHAT_BLACK);
1029 pixSwapAndDestroy(&pix1, &pix2);
1030 break;
1031 default:
1032 /* All invalid ops are caught in the first pass */
1033 break;
1034 }
1035 LEPT_FREE(op);
1036
1037 /* Debug output */
1038 if (dispsep > 0) {
1039 pixDisplay(pix1, x, dispy);
1040 x += dispsep;
1041 }
1042 if (pdfout)
1043 pixaAddPix(pixa, pix1, L_COPY);
1044 }
1045
1046 if (pdfout) {
1047 snprintf(fname, sizeof(fname), "/tmp/lept/seq_output_%d.pdf",
1048 L_ABS(dispsep));
1049 pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname);
1050 pixaDestroy(&pixa);
1051 }
1052
1053 sarrayDestroy(&sa);
1054 return pix1;
1055 }
1056
1057
1058 /*-----------------------------------------------------------------*
1059 * Run a sequence of color morphological operations *
1060 *-----------------------------------------------------------------*/
1061 /*!
1062 * \brief pixColorMorphSequence()
1063 *
1064 * \param[in] pixs
1065 * \param[in] sequence string specifying sequence
1066 * \param[in] dispsep controls debug display of results in the sequence:
1067 * 0: no output
1068 * > 0: gives horizontal separation in pixels between
1069 * successive displays
1070 * < 0: pdf output; abs(dispsep) is used for naming
1071 * \param[in] dispy if dispsep > 0, this gives the y-value of the
1072 * UL corner for display; otherwise it is ignored
1073 * \return pixd, or NULL on error
1074 *
1075 * <pre>
1076 * Notes:
1077 * (1) This works on 32 bpp rgb images.
1078 * (2) Each component is processed separately.
1079 * (3) This runs a pipeline of operations; no branching is allowed.
1080 * (4) This only uses brick SELs.
1081 * (5) A new image is always produced; the input image is not changed.
1082 * (6) This contains an interpreter, allowing sequences to be
1083 * generated and run.
1084 * (7) Sel sizes (width, height) must each be odd numbers.
1085 * (8) The format of the sequence string is defined below.
1086 * (9) Intermediate results can optionally be displayed.
1087 * (10) The sequence string is formatted as follows:
1088 * ~ An arbitrary number of operations, each separated
1089 * by a '+' character. White space is ignored.
1090 * ~ Each operation begins with a case-independent character
1091 * specifying the operation:
1092 * d or D (dilation)
1093 * e or E (erosion)
1094 * o or O (opening)
1095 * c or C (closing)
1096 * ~ The args to the morphological operations are bricks of hits,
1097 * and are formatted as a.b, where a and b are horizontal and
1098 * vertical dimensions, rsp. (each must be an odd number)
1099 * Example valid sequences are:
1100 * "c5.3 + o7.5"
1101 * "D9.1"
1102 * </pre>
1103 */
1104 PIX *
1105 pixColorMorphSequence(PIX *pixs,
1106 const char *sequence,
1107 l_int32 dispsep,
1108 l_int32 dispy)
1109 {
1110 char *rawop, *op;
1111 char fname[256];
1112 l_int32 nops, i, valid, w, h, x, pdfout;
1113 PIX *pix1, *pix2;
1114 PIXA *pixa;
1115 SARRAY *sa;
1116
1117 if (!pixs)
1118 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
1119 if (!sequence)
1120 return (PIX *)ERROR_PTR("sequence not defined", __func__, NULL);
1121
1122 /* Split sequence into individual operations */
1123 sa = sarrayCreate(0);
1124 sarraySplitString(sa, sequence, "+");
1125 nops = sarrayGetCount(sa);
1126 pdfout = (dispsep < 0) ? 1 : 0;
1127
1128 /* Verify that the operation sequence is valid */
1129 valid = TRUE;
1130 for (i = 0; i < nops; i++) {
1131 rawop = sarrayGetString(sa, i, L_NOCOPY);
1132 op = stringRemoveChars(rawop, " \n\t");
1133 switch (op[0])
1134 {
1135 case 'd':
1136 case 'D':
1137 case 'e':
1138 case 'E':
1139 case 'o':
1140 case 'O':
1141 case 'c':
1142 case 'C':
1143 if (sscanf(&op[1], "%d.%d", &w, &h) != 2) {
1144 lept_stderr("*** op: %s invalid\n", op);
1145 valid = FALSE;
1146 break;
1147 }
1148 if (w < 1 || (w & 1) == 0 || h < 1 || (h & 1) == 0 ) {
1149 lept_stderr("*** op: %s; w = %d, h = %d; must both be odd\n",
1150 op, w, h);
1151 valid = FALSE;
1152 break;
1153 }
1154 /* lept_stderr("op = %s; w = %d, h = %d\n", op, w, h); */
1155 break;
1156 default:
1157 lept_stderr("*** nonexistent op = %s\n", op);
1158 valid = FALSE;
1159 }
1160 LEPT_FREE(op);
1161 }
1162 if (!valid) {
1163 sarrayDestroy(&sa);
1164 return (PIX *)ERROR_PTR("sequence invalid", __func__, NULL);
1165 }
1166
1167 /* Parse and operate */
1168 pixa = NULL;
1169 if (pdfout) {
1170 pixa = pixaCreate(0);
1171 pixaAddPix(pixa, pixs, L_CLONE);
1172 }
1173 pix1 = pixCopy(NULL, pixs);
1174 pix2 = NULL;
1175 x = 0;
1176 for (i = 0; i < nops; i++) {
1177 rawop = sarrayGetString(sa, i, L_NOCOPY);
1178 op = stringRemoveChars(rawop, " \n\t");
1179 switch (op[0])
1180 {
1181 case 'd':
1182 case 'D':
1183 sscanf(&op[1], "%d.%d", &w, &h);
1184 pix2 = pixColorMorph(pix1, L_MORPH_DILATE, w, h);
1185 pixSwapAndDestroy(&pix1, &pix2);
1186 break;
1187 case 'e':
1188 case 'E':
1189 sscanf(&op[1], "%d.%d", &w, &h);
1190 pix2 = pixColorMorph(pix1, L_MORPH_ERODE, w, h);
1191 pixSwapAndDestroy(&pix1, &pix2);
1192 break;
1193 case 'o':
1194 case 'O':
1195 sscanf(&op[1], "%d.%d", &w, &h);
1196 pix2 = pixColorMorph(pix1, L_MORPH_OPEN, w, h);
1197 pixSwapAndDestroy(&pix1, &pix2);
1198 break;
1199 case 'c':
1200 case 'C':
1201 sscanf(&op[1], "%d.%d", &w, &h);
1202 pix2 = pixColorMorph(pix1, L_MORPH_CLOSE, w, h);
1203 pixSwapAndDestroy(&pix1, &pix2);
1204 break;
1205 default:
1206 /* All invalid ops are caught in the first pass */
1207 break;
1208 }
1209 LEPT_FREE(op);
1210
1211 /* Debug output */
1212 if (dispsep > 0) {
1213 pixDisplay(pix1, x, dispy);
1214 x += dispsep;
1215 }
1216 if (pdfout)
1217 pixaAddPix(pixa, pix1, L_COPY);
1218 }
1219
1220 if (pdfout) {
1221 snprintf(fname, sizeof(fname), "/tmp/lept/seq_output_%d.pdf",
1222 L_ABS(dispsep));
1223 pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname);
1224 pixaDestroy(&pixa);
1225 }
1226
1227 sarrayDestroy(&sa);
1228 return pix1;
1229 }