comparison mupdf-source/thirdparty/leptonica/src/graymorph.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 /*!
29 * \file graymorph.c
30 * <pre>
31 *
32 * Top-level grayscale morphological operations (van Herk / Gil-Werman)
33 * PIX *pixErodeGray()
34 * PIX *pixDilateGray()
35 * PIX *pixOpenGray()
36 * PIX *pixCloseGray()
37 *
38 * Special operations for 1x3, 3x1 and 3x3 Sels (direct)
39 * PIX *pixErodeGray3()
40 * static PIX *pixErodeGray3h()
41 * static PIX *pixErodeGray3v()
42 * PIX *pixDilateGray3()
43 * static PIX *pixDilateGray3h()
44 * static PIX *pixDilateGray3v()
45 * PIX *pixOpenGray3()
46 * PIX *pixCloseGray3()
47 *
48 * Low-level grayscale morphological operations
49 * static void dilateGrayLow()
50 * static void erodeGrayLow()
51 *
52 *
53 * Method: Algorithm by van Herk and Gil and Werman, 1992
54 *
55 * Measured speed of the vH/G-W implementation is about 1 output
56 * pixel per 120 PIII clock cycles, for a horizontal or vertical
57 * erosion or dilation. The computation time doubles for opening
58 * or closing, or for a square SE, as expected, and is independent
59 * of the size of the SE.
60 *
61 * A faster implementation can be made directly for brick Sels
62 * of maximum size 3. We unroll the computation for sets of 8 bytes.
63 * It needs to be called explicitly; the general functions do not
64 * default for the size 3 brick Sels.
65 *
66 * We use the van Herk/Gil-Werman (vHGW) algorithm, [van Herk,
67 * Patt. Recog. Let. 13, pp. 517-521, 1992; Gil and Werman,
68 * IEEE Trans PAMI 15(5), pp. 504-507, 1993.]
69 * This was the first grayscale morphology
70 * algorithm to compute dilation and erosion with
71 * complexity independent of the size of the structuring
72 * element. It is simple and elegant, and surprising that
73 * it was discovered as recently as 1992. It works for
74 * SEs composed of horizontal and/or vertical lines. The
75 * general case requires finding the Min or Max over an
76 * arbitrary set of pixels, and this requires a number of
77 * pixel comparisons equal to the SE "size" at each pixel
78 * in the image. The vHGW algorithm requires not
79 * more than 3 comparisons at each point. The algorithm has been
80 * recently refined by Gil and Kimmel ("Efficient Dilation
81 * Erosion, Opening and Closing Algorithms", in "Mathematical
82 * Morphology and its Applications to Image and Signal Processing",
83 * the proceedings of the International Symposium on Mathematical
84 * Morphology, Palo Alto, CA, June 2000, Kluwer Academic
85 * Publishers, pp. 301-310). They bring this number down below
86 * 1.5 comparisons per output pixel but at a cost of significantly
87 * increased complexity, so I don't bother with that here.
88 *
89 * In brief, the method is as follows. We evaluate the dilation
90 * in groups of "size" pixels, equal to the size of the SE.
91 * For horizontal, we start at x = "size"/2 and go
92 * (w - 2 * ("size"/2))/"size" steps. This means that
93 * we don't evaluate the first 0.5 * "size" pixels and, worst
94 * case, the last 1.5 * "size" pixels. Thus we embed the
95 * image in a larger image with these augmented dimensions, where
96 * the new border pixels are appropriately initialized (0 for
97 * dilation; 255 for erosion), and remove the boundary at the end.
98 * (For vertical, use h instead of w.) Then for each group
99 * of "size" pixels, we form an array of length 2 * "size" + 1,
100 * consisting of backward and forward partial maxima (for
101 * dilation) or minima (for erosion). This represents a
102 * jumping window computed from the source image, over which
103 * the SE will slide. The center of the array gets the source
104 * pixel at the center of the SE. Call this the center pixel
105 * of the window. Array values to left of center get
106 * the maxima(minima) of the pixels from the center
107 * one and going to the left an equal distance. Array
108 * values to the right of center get the maxima(minima) to
109 * the pixels from the center one and going to the right
110 * an equal distance. These are computed sequentially starting
111 * from the center one. The SE (of length "size") can slide over this
112 * window (of length 2 * "size + 1) at "size" different places.
113 * At each place, the maxima(minima) of the values in the window
114 * that correspond to the end points of the SE give the extremal
115 * values over that interval, and these are stored at the dest
116 * pixel corresponding to the SE center. A picture is worth
117 * at least this many words, so if this isn't clear, see the
118 * leptonica documentation on grayscale morphology.
119 * </pre>
120 */
121
122 #ifdef HAVE_CONFIG_H
123 #include <config_auto.h>
124 #endif /* HAVE_CONFIG_H */
125
126 #include "allheaders.h"
127
128 /* Special static operations for 3x1, 1x3 and 3x3 structuring elements */
129 static PIX *pixErodeGray3h(PIX *pixs);
130 static PIX *pixErodeGray3v(PIX *pixs);
131 static PIX *pixDilateGray3h(PIX *pixs);
132 static PIX *pixDilateGray3v(PIX *pixs);
133
134 /* Low-level gray morphological operations */
135 static void dilateGrayLow(l_uint32 *datad, l_int32 w, l_int32 h,
136 l_int32 wpld, l_uint32 *datas, l_int32 wpls,
137 l_int32 size, l_int32 direction, l_uint8 *buffer,
138 l_uint8 *maxarray);
139 static void erodeGrayLow(l_uint32 *datad, l_int32 w, l_int32 h,
140 l_int32 wpld, l_uint32 *datas, l_int32 wpls,
141 l_int32 size, l_int32 direction, l_uint8 *buffer,
142 l_uint8 *minarray);
143
144 /*-----------------------------------------------------------------*
145 * Top-level grayscale morphological operations *
146 *-----------------------------------------------------------------*/
147 /*!
148 * \brief pixErodeGray()
149 *
150 * \param[in] pixs
151 * \param[in] hsize of Sel; must be odd; origin implicitly in center
152 * \param[in] vsize ditto
153 * \return pixd
154 *
155 * <pre>
156 * Notes:
157 * (1) Sel is a brick with all elements being hits
158 * (2) If hsize = vsize = 1, just returns a copy.
159 * </pre>
160 */
161 PIX *
162 pixErodeGray(PIX *pixs,
163 l_int32 hsize,
164 l_int32 vsize)
165 {
166 l_uint8 *buffer, *minarray;
167 l_int32 w, h, wplb, wplt;
168 l_int32 leftpix, rightpix, toppix, bottompix, maxsize;
169 l_uint32 *datab, *datat;
170 PIX *pixb, *pixt, *pixd;
171
172 if (!pixs)
173 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
174 if (pixGetDepth(pixs) != 8)
175 return (PIX *)ERROR_PTR("pixs not 8 bpp", __func__, NULL);
176 if (hsize < 1 || vsize < 1)
177 return (PIX *)ERROR_PTR("hsize or vsize < 1", __func__, NULL);
178 if ((hsize & 1) == 0 ) {
179 L_WARNING("horiz sel size must be odd; increasing by 1\n", __func__);
180 hsize++;
181 }
182 if ((vsize & 1) == 0 ) {
183 L_WARNING("vert sel size must be odd; increasing by 1\n", __func__);
184 vsize++;
185 }
186
187 pixb = pixt = pixd = NULL;
188 buffer = minarray = NULL;
189
190 if (hsize == 1 && vsize == 1)
191 return pixCopy(NULL, pixs);
192
193 if (vsize == 1) { /* horizontal sel */
194 leftpix = (hsize + 1) / 2;
195 rightpix = (3 * hsize + 1) / 2;
196 toppix = 0;
197 bottompix = 0;
198 } else if (hsize == 1) { /* vertical sel */
199 leftpix = 0;
200 rightpix = 0;
201 toppix = (vsize + 1) / 2;
202 bottompix = (3 * vsize + 1) / 2;
203 } else {
204 leftpix = (hsize + 1) / 2;
205 rightpix = (3 * hsize + 1) / 2;
206 toppix = (vsize + 1) / 2;
207 bottompix = (3 * vsize + 1) / 2;
208 }
209
210 pixb = pixAddBorderGeneral(pixs, leftpix, rightpix, toppix, bottompix, 255);
211 pixt = pixCreateTemplate(pixb);
212 if (!pixb || !pixt) {
213 L_ERROR("pixb and pixt not made\n", __func__);
214 goto cleanup;
215 }
216
217 pixGetDimensions(pixt, &w, &h, NULL);
218 datab = pixGetData(pixb);
219 datat = pixGetData(pixt);
220 wplb = pixGetWpl(pixb);
221 wplt = pixGetWpl(pixt);
222
223 buffer = (l_uint8 *)LEPT_CALLOC(L_MAX(w, h), sizeof(l_uint8));
224 maxsize = L_MAX(hsize, vsize);
225 minarray = (l_uint8 *)LEPT_CALLOC(2 * maxsize, sizeof(l_uint8));
226 if (!buffer || !minarray) {
227 L_ERROR("buffer and minarray not made\n", __func__);
228 goto cleanup;
229 }
230
231 if (vsize == 1) {
232 erodeGrayLow(datat, w, h, wplt, datab, wplb, hsize, L_HORIZ,
233 buffer, minarray);
234 } else if (hsize == 1) {
235 erodeGrayLow(datat, w, h, wplt, datab, wplb, vsize, L_VERT,
236 buffer, minarray);
237 } else {
238 erodeGrayLow(datat, w, h, wplt, datab, wplb, hsize, L_HORIZ,
239 buffer, minarray);
240 pixSetOrClearBorder(pixt, leftpix, rightpix, toppix, bottompix,
241 PIX_SET);
242 erodeGrayLow(datab, w, h, wplb, datat, wplt, vsize, L_VERT,
243 buffer, minarray);
244 pixDestroy(&pixt);
245 pixt = pixClone(pixb);
246 }
247
248 pixd = pixRemoveBorderGeneral(pixt, leftpix, rightpix, toppix, bottompix);
249 if (!pixd)
250 L_ERROR("pixd not made\n", __func__);
251
252 cleanup:
253 LEPT_FREE(buffer);
254 LEPT_FREE(minarray);
255 pixDestroy(&pixb);
256 pixDestroy(&pixt);
257 return pixd;
258 }
259
260
261 /*!
262 * \brief pixDilateGray()
263 *
264 * \param[in] pixs
265 * \param[in] hsize of Sel; must be odd; origin implicitly in center
266 * \param[in] vsize ditto
267 * \return pixd
268 *
269 * <pre>
270 * Notes:
271 * (1) Sel is a brick with all elements being hits
272 * (2) If hsize = vsize = 1, just returns a copy.
273 * </pre>
274 */
275 PIX *
276 pixDilateGray(PIX *pixs,
277 l_int32 hsize,
278 l_int32 vsize)
279 {
280 l_uint8 *buffer, *maxarray;
281 l_int32 w, h, wplb, wplt;
282 l_int32 leftpix, rightpix, toppix, bottompix, maxsize;
283 l_uint32 *datab, *datat;
284 PIX *pixb, *pixt, *pixd;
285
286 if (!pixs)
287 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
288 if (pixGetDepth(pixs) != 8)
289 return (PIX *)ERROR_PTR("pixs not 8 bpp", __func__, NULL);
290 if (hsize < 1 || vsize < 1)
291 return (PIX *)ERROR_PTR("hsize or vsize < 1", __func__, NULL);
292 if ((hsize & 1) == 0 ) {
293 L_WARNING("horiz sel size must be odd; increasing by 1\n", __func__);
294 hsize++;
295 }
296 if ((vsize & 1) == 0 ) {
297 L_WARNING("vert sel size must be odd; increasing by 1\n", __func__);
298 vsize++;
299 }
300
301 pixb = pixt = pixd = NULL;
302 buffer = maxarray = NULL;
303
304 if (hsize == 1 && vsize == 1)
305 return pixCopy(NULL, pixs);
306
307 if (vsize == 1) { /* horizontal sel */
308 leftpix = (hsize + 1) / 2;
309 rightpix = (3 * hsize + 1) / 2;
310 toppix = 0;
311 bottompix = 0;
312 } else if (hsize == 1) { /* vertical sel */
313 leftpix = 0;
314 rightpix = 0;
315 toppix = (vsize + 1) / 2;
316 bottompix = (3 * vsize + 1) / 2;
317 } else {
318 leftpix = (hsize + 1) / 2;
319 rightpix = (3 * hsize + 1) / 2;
320 toppix = (vsize + 1) / 2;
321 bottompix = (3 * vsize + 1) / 2;
322 }
323
324 pixb = pixAddBorderGeneral(pixs, leftpix, rightpix, toppix, bottompix, 0);
325 pixt = pixCreateTemplate(pixb);
326 if (!pixb || !pixt) {
327 L_ERROR("pixb and pixt not made\n", __func__);
328 goto cleanup;
329 }
330
331 pixGetDimensions(pixt, &w, &h, NULL);
332 datab = pixGetData(pixb);
333 datat = pixGetData(pixt);
334 wplb = pixGetWpl(pixb);
335 wplt = pixGetWpl(pixt);
336
337 buffer = (l_uint8 *)LEPT_CALLOC(L_MAX(w, h), sizeof(l_uint8));
338 maxsize = L_MAX(hsize, vsize);
339 maxarray = (l_uint8 *)LEPT_CALLOC(2 * maxsize, sizeof(l_uint8));
340 if (!buffer || !maxarray) {
341 L_ERROR("buffer and maxarray not made\n", __func__);
342 goto cleanup;
343 }
344
345 if (vsize == 1) {
346 dilateGrayLow(datat, w, h, wplt, datab, wplb, hsize, L_HORIZ,
347 buffer, maxarray);
348 } else if (hsize == 1) {
349 dilateGrayLow(datat, w, h, wplt, datab, wplb, vsize, L_VERT,
350 buffer, maxarray);
351 } else {
352 dilateGrayLow(datat, w, h, wplt, datab, wplb, hsize, L_HORIZ,
353 buffer, maxarray);
354 pixSetOrClearBorder(pixt, leftpix, rightpix, toppix, bottompix,
355 PIX_CLR);
356 dilateGrayLow(datab, w, h, wplb, datat, wplt, vsize, L_VERT,
357 buffer, maxarray);
358 pixDestroy(&pixt);
359 pixt = pixClone(pixb);
360 }
361
362 pixd = pixRemoveBorderGeneral(pixt, leftpix, rightpix, toppix, bottompix);
363 if (!pixd)
364 L_ERROR("pixd not made\n", __func__);
365
366 cleanup:
367 LEPT_FREE(buffer);
368 LEPT_FREE(maxarray);
369 pixDestroy(&pixb);
370 pixDestroy(&pixt);
371 return pixd;
372 }
373
374
375 /*!
376 * \brief pixOpenGray()
377 *
378 * \param[in] pixs
379 * \param[in] hsize of Sel; must be odd; origin implicitly in center
380 * \param[in] vsize ditto
381 * \return pixd
382 *
383 * <pre>
384 * Notes:
385 * (1) Sel is a brick with all elements being hits
386 * (2) If hsize = vsize = 1, just returns a copy.
387 * </pre>
388 */
389 PIX *
390 pixOpenGray(PIX *pixs,
391 l_int32 hsize,
392 l_int32 vsize)
393 {
394 l_uint8 *buffer;
395 l_uint8 *array; /* used to find either min or max in interval */
396 l_int32 w, h, wplb, wplt;
397 l_int32 leftpix, rightpix, toppix, bottompix, maxsize;
398 l_uint32 *datab, *datat;
399 PIX *pixb, *pixt, *pixd;
400
401 if (!pixs)
402 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
403 if (pixGetDepth(pixs) != 8)
404 return (PIX *)ERROR_PTR("pixs not 8 bpp", __func__, NULL);
405 if (hsize < 1 || vsize < 1)
406 return (PIX *)ERROR_PTR("hsize or vsize < 1", __func__, NULL);
407 if ((hsize & 1) == 0 ) {
408 L_WARNING("horiz sel size must be odd; increasing by 1\n", __func__);
409 hsize++;
410 }
411 if ((vsize & 1) == 0 ) {
412 L_WARNING("vert sel size must be odd; increasing by 1\n", __func__);
413 vsize++;
414 }
415
416 pixb = pixt = pixd = NULL;
417 buffer = array = NULL;
418
419 if (hsize == 1 && vsize == 1)
420 return pixCopy(NULL, pixs);
421
422 if (vsize == 1) { /* horizontal sel */
423 leftpix = (hsize + 1) / 2;
424 rightpix = (3 * hsize + 1) / 2;
425 toppix = 0;
426 bottompix = 0;
427 } else if (hsize == 1) { /* vertical sel */
428 leftpix = 0;
429 rightpix = 0;
430 toppix = (vsize + 1) / 2;
431 bottompix = (3 * vsize + 1) / 2;
432 } else {
433 leftpix = (hsize + 1) / 2;
434 rightpix = (3 * hsize + 1) / 2;
435 toppix = (vsize + 1) / 2;
436 bottompix = (3 * vsize + 1) / 2;
437 }
438
439 pixb = pixAddBorderGeneral(pixs, leftpix, rightpix, toppix, bottompix, 255);
440 pixt = pixCreateTemplate(pixb);
441 if (!pixb || !pixt) {
442 L_ERROR("pixb and pixt not made\n", __func__);
443 goto cleanup;
444 }
445
446 pixGetDimensions(pixt, &w, &h, NULL);
447 datab = pixGetData(pixb);
448 datat = pixGetData(pixt);
449 wplb = pixGetWpl(pixb);
450 wplt = pixGetWpl(pixt);
451
452 buffer = (l_uint8 *)LEPT_CALLOC(L_MAX(w, h), sizeof(l_uint8));
453 maxsize = L_MAX(hsize, vsize);
454 array = (l_uint8 *)LEPT_CALLOC(2 * maxsize, sizeof(l_uint8));
455 if (!buffer || !array) {
456 L_ERROR("buffer and array not made\n", __func__);
457 goto cleanup;
458 }
459
460 if (vsize == 1) {
461 erodeGrayLow(datat, w, h, wplt, datab, wplb, hsize, L_HORIZ,
462 buffer, array);
463 pixSetOrClearBorder(pixt, leftpix, rightpix, toppix, bottompix,
464 PIX_CLR);
465 dilateGrayLow(datab, w, h, wplb, datat, wplt, hsize, L_HORIZ,
466 buffer, array);
467 }
468 else if (hsize == 1) {
469 erodeGrayLow(datat, w, h, wplt, datab, wplb, vsize, L_VERT,
470 buffer, array);
471 pixSetOrClearBorder(pixt, leftpix, rightpix, toppix, bottompix,
472 PIX_CLR);
473 dilateGrayLow(datab, w, h, wplb, datat, wplt, vsize, L_VERT,
474 buffer, array);
475 } else {
476 erodeGrayLow(datat, w, h, wplt, datab, wplb, hsize, L_HORIZ,
477 buffer, array);
478 pixSetOrClearBorder(pixt, leftpix, rightpix, toppix, bottompix,
479 PIX_SET);
480 erodeGrayLow(datab, w, h, wplb, datat, wplt, vsize, L_VERT,
481 buffer, array);
482 pixSetOrClearBorder(pixb, leftpix, rightpix, toppix, bottompix,
483 PIX_CLR);
484 dilateGrayLow(datat, w, h, wplt, datab, wplb, hsize, L_HORIZ,
485 buffer, array);
486 pixSetOrClearBorder(pixt, leftpix, rightpix, toppix, bottompix,
487 PIX_CLR);
488 dilateGrayLow(datab, w, h, wplb, datat, wplt, vsize, L_VERT,
489 buffer, array);
490 }
491
492 pixd = pixRemoveBorderGeneral(pixb, leftpix, rightpix, toppix, bottompix);
493 if (!pixd)
494 L_ERROR("pixd not made\n", __func__);
495
496 cleanup:
497 LEPT_FREE(buffer);
498 LEPT_FREE(array);
499 pixDestroy(&pixb);
500 pixDestroy(&pixt);
501 return pixd;
502 }
503
504
505 /*!
506 * \brief pixCloseGray()
507 *
508 * \param[in] pixs
509 * \param[in] hsize of Sel; must be odd; origin implicitly in center
510 * \param[in] vsize ditto
511 * \return pixd
512 *
513 * <pre>
514 * Notes:
515 * (1) Sel is a brick with all elements being hits
516 * (2) If hsize = vsize = 1, just returns a copy.
517 * </pre>
518 */
519 PIX *
520 pixCloseGray(PIX *pixs,
521 l_int32 hsize,
522 l_int32 vsize)
523 {
524 l_uint8 *buffer;
525 l_uint8 *array; /* used to find either min or max in interval */
526 l_int32 w, h, wplb, wplt;
527 l_int32 leftpix, rightpix, toppix, bottompix, maxsize;
528 l_uint32 *datab, *datat;
529 PIX *pixb, *pixt, *pixd;
530
531 if (!pixs)
532 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
533 if (pixGetDepth(pixs) != 8)
534 return (PIX *)ERROR_PTR("pixs not 8 bpp", __func__, NULL);
535 if (hsize < 1 || vsize < 1)
536 return (PIX *)ERROR_PTR("hsize or vsize < 1", __func__, NULL);
537 if ((hsize & 1) == 0 ) {
538 L_WARNING("horiz sel size must be odd; increasing by 1\n", __func__);
539 hsize++;
540 }
541 if ((vsize & 1) == 0 ) {
542 L_WARNING("vert sel size must be odd; increasing by 1\n", __func__);
543 vsize++;
544 }
545
546 pixb = pixt = pixd = NULL;
547 buffer = array = NULL;
548
549 if (hsize == 1 && vsize == 1)
550 return pixCopy(NULL, pixs);
551
552 if (vsize == 1) { /* horizontal sel */
553 leftpix = (hsize + 1) / 2;
554 rightpix = (3 * hsize + 1) / 2;
555 toppix = 0;
556 bottompix = 0;
557 } else if (hsize == 1) { /* vertical sel */
558 leftpix = 0;
559 rightpix = 0;
560 toppix = (vsize + 1) / 2;
561 bottompix = (3 * vsize + 1) / 2;
562 } else {
563 leftpix = (hsize + 1) / 2;
564 rightpix = (3 * hsize + 1) / 2;
565 toppix = (vsize + 1) / 2;
566 bottompix = (3 * vsize + 1) / 2;
567 }
568
569 pixb = pixAddBorderGeneral(pixs, leftpix, rightpix, toppix, bottompix, 0);
570 pixt = pixCreateTemplate(pixb);
571 if (!pixb || !pixt) {
572 L_ERROR("pixb and pixt not made\n", __func__);
573 goto cleanup;
574 }
575
576 pixGetDimensions(pixt, &w, &h, NULL);
577 datab = pixGetData(pixb);
578 datat = pixGetData(pixt);
579 wplb = pixGetWpl(pixb);
580 wplt = pixGetWpl(pixt);
581
582 buffer = (l_uint8 *)LEPT_CALLOC(L_MAX(w, h), sizeof(l_uint8));
583 maxsize = L_MAX(hsize, vsize);
584 array = (l_uint8 *)LEPT_CALLOC(2 * maxsize, sizeof(l_uint8));
585 if (!buffer || !array) {
586 L_ERROR("buffer and array not made\n", __func__);
587 goto cleanup;
588 }
589
590 if (vsize == 1) {
591 dilateGrayLow(datat, w, h, wplt, datab, wplb, hsize, L_HORIZ,
592 buffer, array);
593 pixSetOrClearBorder(pixt, leftpix, rightpix, toppix, bottompix,
594 PIX_SET);
595 erodeGrayLow(datab, w, h, wplb, datat, wplt, hsize, L_HORIZ,
596 buffer, array);
597 } else if (hsize == 1) {
598 dilateGrayLow(datat, w, h, wplt, datab, wplb, vsize, L_VERT,
599 buffer, array);
600 pixSetOrClearBorder(pixt, leftpix, rightpix, toppix, bottompix,
601 PIX_SET);
602 erodeGrayLow(datab, w, h, wplb, datat, wplt, vsize, L_VERT,
603 buffer, array);
604 } else {
605 dilateGrayLow(datat, w, h, wplt, datab, wplb, hsize, L_HORIZ,
606 buffer, array);
607 pixSetOrClearBorder(pixt, leftpix, rightpix, toppix, bottompix,
608 PIX_CLR);
609 dilateGrayLow(datab, w, h, wplb, datat, wplt, vsize, L_VERT,
610 buffer, array);
611 pixSetOrClearBorder(pixb, leftpix, rightpix, toppix, bottompix,
612 PIX_SET);
613 erodeGrayLow(datat, w, h, wplt, datab, wplb, hsize, L_HORIZ,
614 buffer, array);
615 pixSetOrClearBorder(pixt, leftpix, rightpix, toppix, bottompix,
616 PIX_SET);
617 erodeGrayLow(datab, w, h, wplb, datat, wplt, vsize, L_VERT,
618 buffer, array);
619 }
620
621 pixd = pixRemoveBorderGeneral(pixb, leftpix, rightpix, toppix, bottompix);
622 if (!pixd)
623 L_ERROR("pixd not made\n", __func__);
624
625 cleanup:
626 LEPT_FREE(buffer);
627 LEPT_FREE(array);
628 pixDestroy(&pixb);
629 pixDestroy(&pixt);
630 return pixd;
631 }
632
633
634 /*-----------------------------------------------------------------*
635 * Special operations for 1x3, 3x1 and 3x3 Sels *
636 *-----------------------------------------------------------------*/
637 /*!
638 * \brief pixErodeGray3()
639 *
640 * \param[in] pixs 8 bpp, not cmapped
641 * \param[in] hsize 1 or 3
642 * \param[in] vsize 1 or 3
643 * \return pixd, or NULL on error
644 *
645 * <pre>
646 * Notes:
647 * (1) Special case for 1x3, 3x1 or 3x3 brick sel (all hits)
648 * (2) If hsize = vsize = 1, just returns a copy.
649 * (3) It would be nice not to add a border, but it is required
650 * if we want the same results as from the general case.
651 * We add 4 bytes on the left to speed up the copying, and
652 * 8 bytes at the right and bottom to allow unrolling of
653 * the computation of 8 pixels.
654 * </pre>
655 */
656 PIX *
657 pixErodeGray3(PIX *pixs,
658 l_int32 hsize,
659 l_int32 vsize)
660 {
661 PIX *pixt, *pixb, *pixbd, *pixd;
662
663 if (!pixs)
664 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
665 if (pixGetDepth(pixs) != 8)
666 return (PIX *)ERROR_PTR("pixs not 8 bpp", __func__, NULL);
667 if (pixGetColormap(pixs))
668 return (PIX *)ERROR_PTR("pix has colormap", __func__, NULL);
669 if ((hsize != 1 && hsize != 3) ||
670 (vsize != 1 && vsize != 3))
671 return (PIX *)ERROR_PTR("invalid size: must be 1 or 3", __func__, NULL);
672
673 if (hsize == 1 && vsize == 1)
674 return pixCopy(NULL, pixs);
675
676 pixb = pixAddBorderGeneral(pixs, 4, 8, 2, 8, 255);
677
678 if (vsize == 1)
679 pixbd = pixErodeGray3h(pixb);
680 else if (hsize == 1)
681 pixbd = pixErodeGray3v(pixb);
682 else { /* vize == hsize == 3 */
683 pixt = pixErodeGray3h(pixb);
684 pixbd = pixErodeGray3v(pixt);
685 pixDestroy(&pixt);
686 }
687
688 pixd = pixRemoveBorderGeneral(pixbd, 4, 8, 2, 8);
689 pixDestroy(&pixb);
690 pixDestroy(&pixbd);
691 return pixd;
692 }
693
694
695 /*!
696 * \brief pixErodeGray3h()
697 *
698 * \param[in] pixs 8 bpp, not cmapped
699 * \return pixd, or NULL on error
700 *
701 * <pre>
702 * Notes:
703 * (1) Special case for horizontal 3x1 brick Sel;
704 * also used as the first step for the 3x3 brick Sel.
705 * </pre>
706 */
707 static PIX *
708 pixErodeGray3h(PIX *pixs)
709 {
710 l_uint32 *datas, *datad, *lines, *lined;
711 l_int32 w, h, wpl, i, j;
712 l_int32 val0, val1, val2, val3, val4, val5, val6, val7, val8, val9, minval;
713 PIX *pixd;
714
715 if (!pixs)
716 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
717 if (pixGetDepth(pixs) != 8)
718 return (PIX *)ERROR_PTR("pixs not 8 bpp", __func__, NULL);
719
720 pixd = pixCreateTemplate(pixs);
721 pixGetDimensions(pixs, &w, &h, NULL);
722 datas = pixGetData(pixs);
723 datad = pixGetData(pixd);
724 wpl = pixGetWpl(pixs);
725 for (i = 0; i < h; i++) {
726 lines = datas + i * wpl;
727 lined = datad + i * wpl;
728 for (j = 1; j < w - 8; j += 8) {
729 val0 = GET_DATA_BYTE(lines, j - 1);
730 val1 = GET_DATA_BYTE(lines, j);
731 val2 = GET_DATA_BYTE(lines, j + 1);
732 val3 = GET_DATA_BYTE(lines, j + 2);
733 val4 = GET_DATA_BYTE(lines, j + 3);
734 val5 = GET_DATA_BYTE(lines, j + 4);
735 val6 = GET_DATA_BYTE(lines, j + 5);
736 val7 = GET_DATA_BYTE(lines, j + 6);
737 val8 = GET_DATA_BYTE(lines, j + 7);
738 val9 = GET_DATA_BYTE(lines, j + 8);
739 minval = L_MIN(val1, val2);
740 SET_DATA_BYTE(lined, j, L_MIN(val0, minval));
741 SET_DATA_BYTE(lined, j + 1, L_MIN(minval, val3));
742 minval = L_MIN(val3, val4);
743 SET_DATA_BYTE(lined, j + 2, L_MIN(val2, minval));
744 SET_DATA_BYTE(lined, j + 3, L_MIN(minval, val5));
745 minval = L_MIN(val5, val6);
746 SET_DATA_BYTE(lined, j + 4, L_MIN(val4, minval));
747 SET_DATA_BYTE(lined, j + 5, L_MIN(minval, val7));
748 minval = L_MIN(val7, val8);
749 SET_DATA_BYTE(lined, j + 6, L_MIN(val6, minval));
750 SET_DATA_BYTE(lined, j + 7, L_MIN(minval, val9));
751 }
752 }
753 return pixd;
754 }
755
756
757 /*!
758 * \brief pixErodeGray3v()
759 *
760 * \param[in] pixs 8 bpp, not cmapped
761 * \return pixd, or NULL on error
762 *
763 * <pre>
764 * Notes:
765 * (1) Special case for vertical 1x3 brick Sel;
766 * also used as the second step for the 3x3 brick Sel.
767 * (2) Surprisingly, this is faster than setting up the
768 * lineptrs array and accessing into it; e.g.,
769 * val4 = GET_DATA_BYTE(lines8[i + 3], j);
770 * </pre>
771 */
772 static PIX *
773 pixErodeGray3v(PIX *pixs)
774 {
775 l_uint32 *datas, *datad, *linesi, *linedi;
776 l_int32 w, h, wpl, i, j;
777 l_int32 val0, val1, val2, val3, val4, val5, val6, val7, val8, val9, minval;
778 PIX *pixd;
779
780 if (!pixs)
781 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
782 if (pixGetDepth(pixs) != 8)
783 return (PIX *)ERROR_PTR("pixs not 8 bpp", __func__, NULL);
784
785 pixd = pixCreateTemplate(pixs);
786 pixGetDimensions(pixs, &w, &h, NULL);
787 datas = pixGetData(pixs);
788 datad = pixGetData(pixd);
789 wpl = pixGetWpl(pixs);
790 for (j = 0; j < w; j++) {
791 for (i = 1; i < h - 8; i += 8) {
792 linesi = datas + i * wpl;
793 linedi = datad + i * wpl;
794 val0 = GET_DATA_BYTE(linesi - wpl, j);
795 val1 = GET_DATA_BYTE(linesi, j);
796 val2 = GET_DATA_BYTE(linesi + wpl, j);
797 val3 = GET_DATA_BYTE(linesi + 2 * wpl, j);
798 val4 = GET_DATA_BYTE(linesi + 3 * wpl, j);
799 val5 = GET_DATA_BYTE(linesi + 4 * wpl, j);
800 val6 = GET_DATA_BYTE(linesi + 5 * wpl, j);
801 val7 = GET_DATA_BYTE(linesi + 6 * wpl, j);
802 val8 = GET_DATA_BYTE(linesi + 7 * wpl, j);
803 val9 = GET_DATA_BYTE(linesi + 8 * wpl, j);
804 minval = L_MIN(val1, val2);
805 SET_DATA_BYTE(linedi, j, L_MIN(val0, minval));
806 SET_DATA_BYTE(linedi + wpl, j, L_MIN(minval, val3));
807 minval = L_MIN(val3, val4);
808 SET_DATA_BYTE(linedi + 2 * wpl, j, L_MIN(val2, minval));
809 SET_DATA_BYTE(linedi + 3 * wpl, j, L_MIN(minval, val5));
810 minval = L_MIN(val5, val6);
811 SET_DATA_BYTE(linedi + 4 * wpl, j, L_MIN(val4, minval));
812 SET_DATA_BYTE(linedi + 5 * wpl, j, L_MIN(minval, val7));
813 minval = L_MIN(val7, val8);
814 SET_DATA_BYTE(linedi + 6 * wpl, j, L_MIN(val6, minval));
815 SET_DATA_BYTE(linedi + 7 * wpl, j, L_MIN(minval, val9));
816 }
817 }
818 return pixd;
819 }
820
821
822 /*!
823 * \brief pixDilateGray3()
824 *
825 * \param[in] pixs 8 bpp, not cmapped
826 * \param[in] hsize 1 or 3
827 * \param[in] vsize 1 or 3
828 * \return pixd, or NULL on error
829 *
830 * <pre>
831 * Notes:
832 * (1) Special case for 1x3, 3x1 or 3x3 brick sel (all hits)
833 * (2) If hsize = vsize = 1, just returns a copy.
834 * </pre>
835 */
836 PIX *
837 pixDilateGray3(PIX *pixs,
838 l_int32 hsize,
839 l_int32 vsize)
840 {
841 PIX *pixt, *pixb, *pixbd, *pixd;
842
843 if (!pixs)
844 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
845 if (pixGetDepth(pixs) != 8)
846 return (PIX *)ERROR_PTR("pixs not 8 bpp", __func__, NULL);
847 if (pixGetColormap(pixs))
848 return (PIX *)ERROR_PTR("pix has colormap", __func__, NULL);
849 if ((hsize != 1 && hsize != 3) ||
850 (vsize != 1 && vsize != 3))
851 return (PIX *)ERROR_PTR("invalid size: must be 1 or 3", __func__, NULL);
852
853 if (hsize == 1 && vsize == 1)
854 return pixCopy(NULL, pixs);
855
856 pixb = pixAddBorderGeneral(pixs, 4, 8, 2, 8, 0);
857
858 if (vsize == 1)
859 pixbd = pixDilateGray3h(pixb);
860 else if (hsize == 1)
861 pixbd = pixDilateGray3v(pixb);
862 else { /* vize == hsize == 3 */
863 pixt = pixDilateGray3h(pixb);
864 pixbd = pixDilateGray3v(pixt);
865 pixDestroy(&pixt);
866 }
867
868 pixd = pixRemoveBorderGeneral(pixbd, 4, 8, 2, 8);
869 pixDestroy(&pixb);
870 pixDestroy(&pixbd);
871 return pixd;
872 }
873
874
875 /*!
876 * \brief pixDilateGray3h()
877 *
878 * \param[in] pixs 8 bpp, not cmapped
879 * \return pixd, or NULL on error
880 *
881 * <pre>
882 * Notes:
883 * (1) Special case for horizontal 3x1 brick Sel;
884 * also used as the first step for the 3x3 brick Sel.
885 * </pre>
886 */
887 static PIX *
888 pixDilateGray3h(PIX *pixs)
889 {
890 l_uint32 *datas, *datad, *lines, *lined;
891 l_int32 w, h, wpl, i, j;
892 l_int32 val0, val1, val2, val3, val4, val5, val6, val7, val8, val9, maxval;
893 PIX *pixd;
894
895 if (!pixs)
896 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
897 if (pixGetDepth(pixs) != 8)
898 return (PIX *)ERROR_PTR("pixs not 8 bpp", __func__, NULL);
899
900 pixd = pixCreateTemplate(pixs);
901 pixGetDimensions(pixs, &w, &h, NULL);
902 datas = pixGetData(pixs);
903 datad = pixGetData(pixd);
904 wpl = pixGetWpl(pixs);
905 for (i = 0; i < h; i++) {
906 lines = datas + i * wpl;
907 lined = datad + i * wpl;
908 for (j = 1; j < w - 8; j += 8) {
909 val0 = GET_DATA_BYTE(lines, j - 1);
910 val1 = GET_DATA_BYTE(lines, j);
911 val2 = GET_DATA_BYTE(lines, j + 1);
912 val3 = GET_DATA_BYTE(lines, j + 2);
913 val4 = GET_DATA_BYTE(lines, j + 3);
914 val5 = GET_DATA_BYTE(lines, j + 4);
915 val6 = GET_DATA_BYTE(lines, j + 5);
916 val7 = GET_DATA_BYTE(lines, j + 6);
917 val8 = GET_DATA_BYTE(lines, j + 7);
918 val9 = GET_DATA_BYTE(lines, j + 8);
919 maxval = L_MAX(val1, val2);
920 SET_DATA_BYTE(lined, j, L_MAX(val0, maxval));
921 SET_DATA_BYTE(lined, j + 1, L_MAX(maxval, val3));
922 maxval = L_MAX(val3, val4);
923 SET_DATA_BYTE(lined, j + 2, L_MAX(val2, maxval));
924 SET_DATA_BYTE(lined, j + 3, L_MAX(maxval, val5));
925 maxval = L_MAX(val5, val6);
926 SET_DATA_BYTE(lined, j + 4, L_MAX(val4, maxval));
927 SET_DATA_BYTE(lined, j + 5, L_MAX(maxval, val7));
928 maxval = L_MAX(val7, val8);
929 SET_DATA_BYTE(lined, j + 6, L_MAX(val6, maxval));
930 SET_DATA_BYTE(lined, j + 7, L_MAX(maxval, val9));
931 }
932 }
933 return pixd;
934 }
935
936
937 /*!
938 * \brief pixDilateGray3v()
939 *
940 * \param[in] pixs 8 bpp, not cmapped
941 * \return pixd, or NULL on error
942 *
943 * <pre>
944 * Notes:
945 * (1) Special case for vertical 1x3 brick Sel;
946 * also used as the second step for the 3x3 brick Sel.
947 * </pre>
948 */
949 static PIX *
950 pixDilateGray3v(PIX *pixs)
951 {
952 l_uint32 *datas, *datad, *linesi, *linedi;
953 l_int32 w, h, wpl, i, j;
954 l_int32 val0, val1, val2, val3, val4, val5, val6, val7, val8, val9, maxval;
955 PIX *pixd;
956
957 if (!pixs)
958 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
959 if (pixGetDepth(pixs) != 8)
960 return (PIX *)ERROR_PTR("pixs not 8 bpp", __func__, NULL);
961
962 pixd = pixCreateTemplate(pixs);
963 pixGetDimensions(pixs, &w, &h, NULL);
964 datas = pixGetData(pixs);
965 datad = pixGetData(pixd);
966 wpl = pixGetWpl(pixs);
967 for (j = 0; j < w; j++) {
968 for (i = 1; i < h - 8; i += 8) {
969 linesi = datas + i * wpl;
970 linedi = datad + i * wpl;
971 val0 = GET_DATA_BYTE(linesi - wpl, j);
972 val1 = GET_DATA_BYTE(linesi, j);
973 val2 = GET_DATA_BYTE(linesi + wpl, j);
974 val3 = GET_DATA_BYTE(linesi + 2 * wpl, j);
975 val4 = GET_DATA_BYTE(linesi + 3 * wpl, j);
976 val5 = GET_DATA_BYTE(linesi + 4 * wpl, j);
977 val6 = GET_DATA_BYTE(linesi + 5 * wpl, j);
978 val7 = GET_DATA_BYTE(linesi + 6 * wpl, j);
979 val8 = GET_DATA_BYTE(linesi + 7 * wpl, j);
980 val9 = GET_DATA_BYTE(linesi + 8 * wpl, j);
981 maxval = L_MAX(val1, val2);
982 SET_DATA_BYTE(linedi, j, L_MAX(val0, maxval));
983 SET_DATA_BYTE(linedi + wpl, j, L_MAX(maxval, val3));
984 maxval = L_MAX(val3, val4);
985 SET_DATA_BYTE(linedi + 2 * wpl, j, L_MAX(val2, maxval));
986 SET_DATA_BYTE(linedi + 3 * wpl, j, L_MAX(maxval, val5));
987 maxval = L_MAX(val5, val6);
988 SET_DATA_BYTE(linedi + 4 * wpl, j, L_MAX(val4, maxval));
989 SET_DATA_BYTE(linedi + 5 * wpl, j, L_MAX(maxval, val7));
990 maxval = L_MAX(val7, val8);
991 SET_DATA_BYTE(linedi + 6 * wpl, j, L_MAX(val6, maxval));
992 SET_DATA_BYTE(linedi + 7 * wpl, j, L_MAX(maxval, val9));
993 }
994 }
995 return pixd;
996 }
997
998
999 /*!
1000 * \brief pixOpenGray3()
1001 *
1002 * \param[in] pixs 8 bpp, not cmapped
1003 * \param[in] hsize 1 or 3
1004 * \param[in] vsize 1 or 3
1005 * \return pixd, or NULL on error
1006 *
1007 * <pre>
1008 * Notes:
1009 * (1) Special case for 1x3, 3x1 or 3x3 brick sel (all hits)
1010 * (2) If hsize = vsize = 1, just returns a copy.
1011 * (3) It would be nice not to add a border, but it is required
1012 * to get the same results as for the general case.
1013 * </pre>
1014 */
1015 PIX *
1016 pixOpenGray3(PIX *pixs,
1017 l_int32 hsize,
1018 l_int32 vsize)
1019 {
1020 PIX *pixt, *pixb, *pixbd, *pixd;
1021
1022 if (!pixs)
1023 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
1024 if (pixGetDepth(pixs) != 8)
1025 return (PIX *)ERROR_PTR("pixs not 8 bpp", __func__, NULL);
1026 if (pixGetColormap(pixs))
1027 return (PIX *)ERROR_PTR("pix has colormap", __func__, NULL);
1028 if ((hsize != 1 && hsize != 3) ||
1029 (vsize != 1 && vsize != 3))
1030 return (PIX *)ERROR_PTR("invalid size: must be 1 or 3", __func__, NULL);
1031
1032 if (hsize == 1 && vsize == 1)
1033 return pixCopy(NULL, pixs);
1034
1035 pixb = pixAddBorderGeneral(pixs, 4, 8, 2, 8, 255); /* set to max */
1036
1037 if (vsize == 1) {
1038 pixt = pixErodeGray3h(pixb);
1039 pixSetBorderVal(pixt, 4, 8, 2, 8, 0); /* set to min */
1040 pixbd = pixDilateGray3h(pixt);
1041 pixDestroy(&pixt);
1042 } else if (hsize == 1) {
1043 pixt = pixErodeGray3v(pixb);
1044 pixSetBorderVal(pixt, 4, 8, 2, 8, 0);
1045 pixbd = pixDilateGray3v(pixt);
1046 pixDestroy(&pixt);
1047 } else { /* vize == hsize == 3 */
1048 pixt = pixErodeGray3h(pixb);
1049 pixbd = pixErodeGray3v(pixt);
1050 pixDestroy(&pixt);
1051 pixSetBorderVal(pixbd, 4, 8, 2, 8, 0);
1052 pixt = pixDilateGray3h(pixbd);
1053 pixDestroy(&pixbd);
1054 pixbd = pixDilateGray3v(pixt);
1055 pixDestroy(&pixt);
1056 }
1057
1058 pixd = pixRemoveBorderGeneral(pixbd, 4, 8, 2, 8);
1059 pixDestroy(&pixb);
1060 pixDestroy(&pixbd);
1061 return pixd;
1062 }
1063
1064
1065 /*!
1066 * \brief pixCloseGray3()
1067 *
1068 * \param[in] pixs 8 bpp, not cmapped
1069 * \param[in] hsize 1 or 3
1070 * \param[in] vsize 1 or 3
1071 * \return pixd, or NULL on error
1072 *
1073 * <pre>
1074 * Notes:
1075 * (1) Special case for 1x3, 3x1 or 3x3 brick sel (all hits)
1076 * (2) If hsize = vsize = 1, just returns a copy.
1077 * </pre>
1078 */
1079 PIX *
1080 pixCloseGray3(PIX *pixs,
1081 l_int32 hsize,
1082 l_int32 vsize)
1083 {
1084 PIX *pixt, *pixb, *pixbd, *pixd;
1085
1086 if (!pixs)
1087 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
1088 if (pixGetDepth(pixs) != 8)
1089 return (PIX *)ERROR_PTR("pixs not 8 bpp", __func__, NULL);
1090 if (pixGetColormap(pixs))
1091 return (PIX *)ERROR_PTR("pix has colormap", __func__, NULL);
1092 if ((hsize != 1 && hsize != 3) ||
1093 (vsize != 1 && vsize != 3))
1094 return (PIX *)ERROR_PTR("invalid size: must be 1 or 3", __func__, NULL);
1095
1096 if (hsize == 1 && vsize == 1)
1097 return pixCopy(NULL, pixs);
1098
1099 pixb = pixAddBorderGeneral(pixs, 4, 8, 2, 8, 0); /* set to min */
1100
1101 if (vsize == 1) {
1102 pixt = pixDilateGray3h(pixb);
1103 pixSetBorderVal(pixt, 4, 8, 2, 8, 255); /* set to max */
1104 pixbd = pixErodeGray3h(pixt);
1105 pixDestroy(&pixt);
1106 } else if (hsize == 1) {
1107 pixt = pixDilateGray3v(pixb);
1108 pixSetBorderVal(pixt, 4, 8, 2, 8, 255);
1109 pixbd = pixErodeGray3v(pixt);
1110 pixDestroy(&pixt);
1111 } else { /* vize == hsize == 3 */
1112 pixt = pixDilateGray3h(pixb);
1113 pixbd = pixDilateGray3v(pixt);
1114 pixDestroy(&pixt);
1115 pixSetBorderVal(pixbd, 4, 8, 2, 8, 255);
1116 pixt = pixErodeGray3h(pixbd);
1117 pixDestroy(&pixbd);
1118 pixbd = pixErodeGray3v(pixt);
1119 pixDestroy(&pixt);
1120 }
1121
1122 pixd = pixRemoveBorderGeneral(pixbd, 4, 8, 2, 8);
1123 pixDestroy(&pixb);
1124 pixDestroy(&pixbd);
1125 return pixd;
1126 }
1127
1128
1129 /*-----------------------------------------------------------------*
1130 * Low-level gray morphological operations *
1131 *-----------------------------------------------------------------*/
1132 /*!
1133 * \brief dilateGrayLow()
1134 *
1135 * \param[in] datad 8 bpp dsst image
1136 * \param[in] w, h dimensions of src and dest
1137 * \param[in] wpld words/line of dest
1138 * \param[in] datas 8 bpp src image
1139 * \param[in] wpls words/line of src
1140 * \param[in] size full length of SEL; restricted to odd numbers
1141 * \param[in] direction L_HORIZ or L_VERT
1142 * \param[in] buffer holds full line or column of src image pixels
1143 * \param[in] maxarray array of dimension 2*size+1
1144 * \return void
1145 *
1146 * <pre>
1147 * Notes:
1148 * (1) To eliminate border effects on the actual image, these images
1149 * are prepared with an additional border of dimensions:
1150 * leftpix = 0.5 * size
1151 * rightpix = 1.5 * size
1152 * toppix = 0.5 * size
1153 * bottompix = 1.5 * size
1154 * and we initialize the src border pixels to 0.
1155 * This allows full processing over the actual image; at
1156 * the end the border is removed.
1157 * (2) Uses algorithm of van Herk, Gil and Werman
1158 * </pre>
1159 */
1160 static void
1161 dilateGrayLow(l_uint32 *datad,
1162 l_int32 w,
1163 l_int32 h,
1164 l_int32 wpld,
1165 l_uint32 *datas,
1166 l_int32 wpls,
1167 l_int32 size,
1168 l_int32 direction,
1169 l_uint8 *buffer,
1170 l_uint8 *maxarray)
1171 {
1172 l_int32 i, j, k;
1173 l_int32 hsize, nsteps, startmax, startx, starty;
1174 l_uint8 maxval;
1175 l_uint32 *lines, *lined;
1176
1177 if (direction == L_HORIZ) {
1178 hsize = size / 2;
1179 nsteps = (w - 2 * hsize) / size;
1180 for (i = 0; i < h; i++) {
1181 lines = datas + i * wpls;
1182 lined = datad + i * wpld;
1183
1184 /* fill buffer with pixels in byte order */
1185 for (j = 0; j < w; j++)
1186 buffer[j] = GET_DATA_BYTE(lines, j);
1187
1188 for (j = 0; j < nsteps; j++) {
1189 /* refill the minarray */
1190 startmax = (j + 1) * size - 1;
1191 maxarray[size - 1] = buffer[startmax];
1192 for (k = 1; k < size; k++) {
1193 maxarray[size - 1 - k] =
1194 L_MAX(maxarray[size - k], buffer[startmax - k]);
1195 maxarray[size - 1 + k] =
1196 L_MAX(maxarray[size + k - 2], buffer[startmax + k]);
1197 }
1198
1199 /* compute dilation values */
1200 startx = hsize + j * size;
1201 SET_DATA_BYTE(lined, startx, maxarray[0]);
1202 SET_DATA_BYTE(lined, startx + size - 1, maxarray[2 * size - 2]);
1203 for (k = 1; k < size - 1; k++) {
1204 maxval = L_MAX(maxarray[k], maxarray[k + size - 1]);
1205 SET_DATA_BYTE(lined, startx + k, maxval);
1206 }
1207 }
1208 }
1209 } else { /* direction == L_VERT */
1210 hsize = size / 2;
1211 nsteps = (h - 2 * hsize) / size;
1212 for (j = 0; j < w; j++) {
1213 /* fill buffer with pixels in byte order */
1214 for (i = 0; i < h; i++) {
1215 lines = datas + i * wpls;
1216 buffer[i] = GET_DATA_BYTE(lines, j);
1217 }
1218
1219 for (i = 0; i < nsteps; i++) {
1220 /* refill the minarray */
1221 startmax = (i + 1) * size - 1;
1222 maxarray[size - 1] = buffer[startmax];
1223 for (k = 1; k < size; k++) {
1224 maxarray[size - 1 - k] =
1225 L_MAX(maxarray[size - k], buffer[startmax - k]);
1226 maxarray[size - 1 + k] =
1227 L_MAX(maxarray[size + k - 2], buffer[startmax + k]);
1228 }
1229
1230 /* compute dilation values */
1231 starty = hsize + i * size;
1232 lined = datad + starty * wpld;
1233 SET_DATA_BYTE(lined, j, maxarray[0]);
1234 SET_DATA_BYTE(lined + (size - 1) * wpld, j,
1235 maxarray[2 * size - 2]);
1236 for (k = 1; k < size - 1; k++) {
1237 maxval = L_MAX(maxarray[k], maxarray[k + size - 1]);
1238 SET_DATA_BYTE(lined + wpld * k, j, maxval);
1239 }
1240 }
1241 }
1242 }
1243
1244 return;
1245 }
1246
1247
1248 /*!
1249 * \brief erodeGrayLow()
1250 *
1251 * \param[in] datad 8 bpp dsst image
1252 * \param[in] w, h dimensions of src and dest
1253 * \param[in] wpld words/line of dest
1254 * \param[in] datas 8 bpp src image
1255 * \param[in] wpls words/line of src
1256 * \param[in] size full length of SEL; restricted to odd numbers
1257 * \param[in] direction L_HORIZ or L_VERT
1258 * \param[in] buffer holds full line or column of src image pixels
1259 * \param[in] minarray array of dimension 2*size+1
1260 * \return void
1261 *
1262 * <pre>
1263 * Notes:
1264 * (1) See notes in dilateGrayLow()
1265 * </pre>
1266 */
1267 static void
1268 erodeGrayLow(l_uint32 *datad,
1269 l_int32 w,
1270 l_int32 h,
1271 l_int32 wpld,
1272 l_uint32 *datas,
1273 l_int32 wpls,
1274 l_int32 size,
1275 l_int32 direction,
1276 l_uint8 *buffer,
1277 l_uint8 *minarray)
1278 {
1279 l_int32 i, j, k;
1280 l_int32 hsize, nsteps, startmin, startx, starty;
1281 l_uint8 minval;
1282 l_uint32 *lines, *lined;
1283
1284 if (direction == L_HORIZ) {
1285 hsize = size / 2;
1286 nsteps = (w - 2 * hsize) / size;
1287 for (i = 0; i < h; i++) {
1288 lines = datas + i * wpls;
1289 lined = datad + i * wpld;
1290
1291 /* fill buffer with pixels in byte order */
1292 for (j = 0; j < w; j++)
1293 buffer[j] = GET_DATA_BYTE(lines, j);
1294
1295 for (j = 0; j < nsteps; j++) {
1296 /* refill the minarray */
1297 startmin = (j + 1) * size - 1;
1298 minarray[size - 1] = buffer[startmin];
1299 for (k = 1; k < size; k++) {
1300 minarray[size - 1 - k] =
1301 L_MIN(minarray[size - k], buffer[startmin - k]);
1302 minarray[size - 1 + k] =
1303 L_MIN(minarray[size + k - 2], buffer[startmin + k]);
1304 }
1305
1306 /* compute erosion values */
1307 startx = hsize + j * size;
1308 SET_DATA_BYTE(lined, startx, minarray[0]);
1309 SET_DATA_BYTE(lined, startx + size - 1, minarray[2 * size - 2]);
1310 for (k = 1; k < size - 1; k++) {
1311 minval = L_MIN(minarray[k], minarray[k + size - 1]);
1312 SET_DATA_BYTE(lined, startx + k, minval);
1313 }
1314 }
1315 }
1316 } else { /* direction == L_VERT */
1317 hsize = size / 2;
1318 nsteps = (h - 2 * hsize) / size;
1319 for (j = 0; j < w; j++) {
1320 /* fill buffer with pixels in byte order */
1321 for (i = 0; i < h; i++) {
1322 lines = datas + i * wpls;
1323 buffer[i] = GET_DATA_BYTE(lines, j);
1324 }
1325
1326 for (i = 0; i < nsteps; i++) {
1327 /* refill the minarray */
1328 startmin = (i + 1) * size - 1;
1329 minarray[size - 1] = buffer[startmin];
1330 for (k = 1; k < size; k++) {
1331 minarray[size - 1 - k] =
1332 L_MIN(minarray[size - k], buffer[startmin - k]);
1333 minarray[size - 1 + k] =
1334 L_MIN(minarray[size + k - 2], buffer[startmin + k]);
1335 }
1336
1337 /* compute erosion values */
1338 starty = hsize + i * size;
1339 lined = datad + starty * wpld;
1340 SET_DATA_BYTE(lined, j, minarray[0]);
1341 SET_DATA_BYTE(lined + (size - 1) * wpld, j,
1342 minarray[2 * size - 2]);
1343 for (k = 1; k < size - 1; k++) {
1344 minval = L_MIN(minarray[k], minarray[k + size - 1]);
1345 SET_DATA_BYTE(lined + wpld * k, j, minval);
1346 }
1347 }
1348 }
1349 }
1350
1351 return;
1352 }