comparison mupdf-source/thirdparty/leptonica/src/pix5.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 pix5.c
29 * <pre>
30 *
31 * This file has these operations:
32 *
33 * (1) Measurement of 1 bpp image properties
34 * (2) Extract rectangular regions
35 * (3) Clip to foreground
36 * (4) Extract pixel averages, reversals and variance along lines
37 * (5) Rank row and column transforms
38 *
39 * Measurement of properties
40 * l_int32 pixaFindDimensions()
41 * l_int32 pixFindAreaPerimRatio()
42 * NUMA *pixaFindPerimToAreaRatio()
43 * l_int32 pixFindPerimToAreaRatio()
44 * NUMA *pixaFindPerimSizeRatio()
45 * l_int32 pixFindPerimSizeRatio()
46 * NUMA *pixaFindAreaFraction()
47 * l_int32 pixFindAreaFraction()
48 * NUMA *pixaFindAreaFractionMasked()
49 * l_int32 pixFindAreaFractionMasked()
50 * NUMA *pixaFindWidthHeightRatio()
51 * NUMA *pixaFindWidthHeightProduct()
52 * l_int32 pixFindOverlapFraction()
53 * BOXA *pixFindRectangleComps()
54 * l_int32 pixConformsToRectangle()
55 *
56 * Extract rectangular regions
57 * PIX *pixExtractRectangularRegions()
58 * PIXA *pixClipRectangles()
59 * PIX *pixClipRectangle()
60 * PIX *pixClipRectangleWithBorder()
61 * PIX *pixClipMasked()
62 * l_int32 pixCropToMatch()
63 * PIX *pixCropToSize()
64 * PIX *pixResizeToMatch()
65 *
66 * Select a connected component by size
67 * PIX *pixSelectComponentBySize()
68 * PIX *pixFilterComponentBySize()
69 *
70 * Make special masks
71 * PIX *pixMakeSymmetricMask()
72 * PIX *pixMakeFrameMask()
73 *
74 * Generate a covering of rectangles over connected components
75 * PIX * pixMakeCoveringOfRectangles()
76 *
77 * Fraction of Fg pixels under a mask
78 * l_int32 pixFractionFgInMask()
79 *
80 * Clip to foreground
81 * PIX *pixClipToForeground()
82 * l_int32 pixTestClipToForeground()
83 * l_int32 pixClipBoxToForeground()
84 * l_int32 pixScanForForeground()
85 * l_int32 pixClipBoxToEdges()
86 * l_int32 pixScanForEdge()
87 *
88 * Extract pixel averages and reversals along lines
89 * NUMA *pixExtractOnLine()
90 * l_float32 pixAverageOnLine()
91 * NUMA *pixAverageIntensityProfile()
92 * NUMA *pixReversalProfile()
93 *
94 * Extract windowed variance along a line
95 * NUMA *pixWindowedVarianceOnLine()
96 *
97 * Extract min/max of pixel values near lines
98 * l_int32 pixMinMaxNearLine()
99 *
100 * Rank row and column transforms
101 * PIX *pixRankRowTransform()
102 * PIX *pixRankColumnTransform()
103 * </pre>
104 */
105
106 #ifdef HAVE_CONFIG_H
107 #include <config_auto.h>
108 #endif /* HAVE_CONFIG_H */
109
110 #include <string.h>
111 #include <math.h>
112 #include "allheaders.h"
113
114 static const l_uint32 rmask32[] = {0x0,
115 0x00000001, 0x00000003, 0x00000007, 0x0000000f,
116 0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff,
117 0x000001ff, 0x000003ff, 0x000007ff, 0x00000fff,
118 0x00001fff, 0x00003fff, 0x00007fff, 0x0000ffff,
119 0x0001ffff, 0x0003ffff, 0x0007ffff, 0x000fffff,
120 0x001fffff, 0x003fffff, 0x007fffff, 0x00ffffff,
121 0x01ffffff, 0x03ffffff, 0x07ffffff, 0x0fffffff,
122 0x1fffffff, 0x3fffffff, 0x7fffffff, 0xffffffff};
123
124 #ifndef NO_CONSOLE_IO
125 #define DEBUG_EDGES 0
126 #endif /* ~NO_CONSOLE_IO */
127
128
129 /*-------------------------------------------------------------*
130 * Measurement of properties *
131 *-------------------------------------------------------------*/
132 /*!
133 * \brief pixaFindDimensions()
134 *
135 * \param[in] pixa
136 * \param[out] pnaw [optional] numa of pix widths
137 * \param[out] pnah [optional] numa of pix heights
138 * \return 0 if OK, 1 on error
139 */
140 l_ok
141 pixaFindDimensions(PIXA *pixa,
142 NUMA **pnaw,
143 NUMA **pnah)
144 {
145 l_int32 i, n, w, h;
146 PIX *pixt;
147
148 if (pnaw) *pnaw = NULL;
149 if (pnah) *pnah = NULL;
150 if (!pnaw && !pnah)
151 return ERROR_INT("no output requested", __func__, 1);
152 if (!pixa)
153 return ERROR_INT("pixa not defined", __func__, 1);
154
155 n = pixaGetCount(pixa);
156 if (pnaw) *pnaw = numaCreate(n);
157 if (pnah) *pnah = numaCreate(n);
158 for (i = 0; i < n; i++) {
159 pixt = pixaGetPix(pixa, i, L_CLONE);
160 pixGetDimensions(pixt, &w, &h, NULL);
161 if (pnaw)
162 numaAddNumber(*pnaw, w);
163 if (pnah)
164 numaAddNumber(*pnah, h);
165 pixDestroy(&pixt);
166 }
167 return 0;
168 }
169
170
171 /*!
172 * \brief pixFindAreaPerimRatio()
173 *
174 * \param[in] pixs 1 bpp
175 * \param[in] tab [optional] pixel sum table, can be NULL
176 * \param[out] pfract area/perimeter ratio
177 * \return 0 if OK, 1 on error
178 *
179 * <pre>
180 * Notes:
181 * (1) The area is the number of fg pixels that are not on the
182 * boundary (i.e., are not 8-connected to a bg pixel), and the
183 * perimeter is the number of fg boundary pixels. Returns
184 * 0.0 if there are no fg pixels.
185 * (2) This function is retained because clients are using it.
186 * </pre>
187 */
188 l_ok
189 pixFindAreaPerimRatio(PIX *pixs,
190 l_int32 *tab,
191 l_float32 *pfract)
192 {
193 l_int32 *tab8;
194 l_int32 nfg, nbound;
195 PIX *pixt;
196
197 if (!pfract)
198 return ERROR_INT("&fract not defined", __func__, 1);
199 *pfract = 0.0;
200 if (!pixs || pixGetDepth(pixs) != 1)
201 return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1);
202
203 if (!tab)
204 tab8 = makePixelSumTab8();
205 else
206 tab8 = tab;
207
208 pixt = pixErodeBrick(NULL, pixs, 3, 3);
209 pixCountPixels(pixt, &nfg, tab8);
210 if (nfg == 0) {
211 pixDestroy(&pixt);
212 if (!tab) LEPT_FREE(tab8);
213 return 0;
214 }
215 pixXor(pixt, pixt, pixs);
216 pixCountPixels(pixt, &nbound, tab8);
217 *pfract = (l_float32)nfg / (l_float32)nbound;
218 pixDestroy(&pixt);
219
220 if (!tab) LEPT_FREE(tab8);
221 return 0;
222 }
223
224
225 /*!
226 * \brief pixaFindPerimToAreaRatio()
227 *
228 * \param[in] pixa of 1 bpp pix
229 * \return na of perimeter/arear ratio for each pix, or NULL on error
230 *
231 * <pre>
232 * Notes:
233 * (1) This is typically used for a pixa consisting of
234 * 1 bpp connected components.
235 * </pre>
236 */
237 NUMA *
238 pixaFindPerimToAreaRatio(PIXA *pixa)
239 {
240 l_int32 i, n;
241 l_int32 *tab;
242 l_float32 fract;
243 NUMA *na;
244 PIX *pixt;
245
246 if (!pixa)
247 return (NUMA *)ERROR_PTR("pixa not defined", __func__, NULL);
248
249 n = pixaGetCount(pixa);
250 na = numaCreate(n);
251 tab = makePixelSumTab8();
252 for (i = 0; i < n; i++) {
253 pixt = pixaGetPix(pixa, i, L_CLONE);
254 pixFindPerimToAreaRatio(pixt, tab, &fract);
255 numaAddNumber(na, fract);
256 pixDestroy(&pixt);
257 }
258 LEPT_FREE(tab);
259 return na;
260 }
261
262
263 /*!
264 * \brief pixFindPerimToAreaRatio()
265 *
266 * \param[in] pixs 1 bpp
267 * \param[in] tab [optional] pixel sum table, can be NULL
268 * \param[out] pfract perimeter/area ratio
269 * \return 0 if OK, 1 on error
270 *
271 * <pre>
272 * Notes:
273 * (1) The perimeter is the number of fg boundary pixels, and the
274 * area is the number of fg pixels. This returns 0.0 if
275 * there are no fg pixels.
276 * (2) Unlike pixFindAreaPerimRatio(), this uses the full set of
277 * fg pixels for the area, and the ratio is taken in the opposite
278 * order.
279 * (3) This is typically used for a single connected component.
280 * This always has a value <= 1.0, and if the average distance
281 * of a fg pixel from the nearest bg pixel is d, this has
282 * a value ~1/d.
283 * </pre>
284 */
285 l_ok
286 pixFindPerimToAreaRatio(PIX *pixs,
287 l_int32 *tab,
288 l_float32 *pfract)
289 {
290 l_int32 *tab8;
291 l_int32 nfg, nbound;
292 PIX *pixt;
293
294 if (!pfract)
295 return ERROR_INT("&fract not defined", __func__, 1);
296 *pfract = 0.0;
297 if (!pixs || pixGetDepth(pixs) != 1)
298 return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1);
299
300 if (!tab)
301 tab8 = makePixelSumTab8();
302 else
303 tab8 = tab;
304
305 pixCountPixels(pixs, &nfg, tab8);
306 if (nfg == 0) {
307 if (!tab) LEPT_FREE(tab8);
308 return 0;
309 }
310 pixt = pixErodeBrick(NULL, pixs, 3, 3);
311 pixXor(pixt, pixt, pixs);
312 pixCountPixels(pixt, &nbound, tab8);
313 *pfract = (l_float32)nbound / (l_float32)nfg;
314 pixDestroy(&pixt);
315
316 if (!tab) LEPT_FREE(tab8);
317 return 0;
318 }
319
320
321 /*!
322 * \brief pixaFindPerimSizeRatio()
323 *
324 * \param[in] pixa of 1 bpp pix
325 * \return na of fg perimeter/(2*(w+h)) ratio for each pix,
326 * or NULL on error
327 *
328 * <pre>
329 * Notes:
330 * (1) This is typically used for a pixa consisting of
331 * 1 bpp connected components.
332 * (2) This has a minimum value for a circle of pi/4; a value for
333 * a rectangle component of approx. 1.0; and a value much larger
334 * than 1.0 for a component with a highly irregular boundary.
335 * </pre>
336 */
337 NUMA *
338 pixaFindPerimSizeRatio(PIXA *pixa)
339 {
340 l_int32 i, n;
341 l_int32 *tab;
342 l_float32 ratio;
343 NUMA *na;
344 PIX *pixt;
345
346 if (!pixa)
347 return (NUMA *)ERROR_PTR("pixa not defined", __func__, NULL);
348
349 n = pixaGetCount(pixa);
350 na = numaCreate(n);
351 tab = makePixelSumTab8();
352 for (i = 0; i < n; i++) {
353 pixt = pixaGetPix(pixa, i, L_CLONE);
354 pixFindPerimSizeRatio(pixt, tab, &ratio);
355 numaAddNumber(na, ratio);
356 pixDestroy(&pixt);
357 }
358 LEPT_FREE(tab);
359 return na;
360 }
361
362
363 /*!
364 * \brief pixFindPerimSizeRatio()
365 *
366 * \param[in] pixs 1 bpp
367 * \param[in] tab [optional] pixel sum table, can be NULL
368 * \param[out] pratio perimeter/size ratio
369 * \return 0 if OK, 1 on error
370 *
371 * <pre>
372 * Notes:
373 * (1) We take the 'size' as twice the sum of the width and
374 * height of pixs, and the perimeter is the number of fg
375 * boundary pixels. We use the fg pixels of the boundary
376 * because the pix may be clipped to the boundary, so an
377 * erosion is required to count all boundary pixels.
378 * (2) This has a large value for dendritic, fractal-like components
379 * with highly irregular boundaries.
380 * (3) This is typically used for a single connected component.
381 * It has a value of about 1.0 for rectangular components with
382 * relatively smooth boundaries.
383 * </pre>
384 */
385 l_ok
386 pixFindPerimSizeRatio(PIX *pixs,
387 l_int32 *tab,
388 l_float32 *pratio)
389 {
390 l_int32 *tab8;
391 l_int32 w, h, nbound;
392 PIX *pixt;
393
394 if (!pratio)
395 return ERROR_INT("&ratio not defined", __func__, 1);
396 *pratio = 0.0;
397 if (!pixs || pixGetDepth(pixs) != 1)
398 return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1);
399
400 if (!tab)
401 tab8 = makePixelSumTab8();
402 else
403 tab8 = tab;
404
405 pixt = pixErodeBrick(NULL, pixs, 3, 3);
406 pixXor(pixt, pixt, pixs);
407 pixCountPixels(pixt, &nbound, tab8);
408 pixGetDimensions(pixs, &w, &h, NULL);
409 *pratio = (0.5f * nbound) / (l_float32)(w + h);
410 pixDestroy(&pixt);
411
412 if (!tab) LEPT_FREE(tab8);
413 return 0;
414 }
415
416
417 /*!
418 * \brief pixaFindAreaFraction()
419 *
420 * \param[in] pixa of 1 bpp pix
421 * \return na of area fractions for each pix, or NULL on error
422 *
423 * <pre>
424 * Notes:
425 * (1) This is typically used for a pixa consisting of
426 * 1 bpp connected components.
427 * </pre>
428 */
429 NUMA *
430 pixaFindAreaFraction(PIXA *pixa)
431 {
432 l_int32 i, n;
433 l_int32 *tab;
434 l_float32 fract;
435 NUMA *na;
436 PIX *pixt;
437
438 if (!pixa)
439 return (NUMA *)ERROR_PTR("pixa not defined", __func__, NULL);
440
441 n = pixaGetCount(pixa);
442 na = numaCreate(n);
443 tab = makePixelSumTab8();
444 for (i = 0; i < n; i++) {
445 pixt = pixaGetPix(pixa, i, L_CLONE);
446 pixFindAreaFraction(pixt, tab, &fract);
447 numaAddNumber(na, fract);
448 pixDestroy(&pixt);
449 }
450 LEPT_FREE(tab);
451 return na;
452 }
453
454
455 /*!
456 * \brief pixFindAreaFraction()
457 *
458 * \param[in] pixs 1 bpp
459 * \param[in] tab [optional] pixel sum table, can be NULL
460 * \param[out] pfract fg area/size ratio
461 * \return 0 if OK, 1 on error
462 *
463 * <pre>
464 * Notes:
465 * (1) This finds the ratio of the number of fg pixels to the
466 * size of the pix (w * h). It is typically used for a
467 * single connected component.
468 * </pre>
469 */
470 l_ok
471 pixFindAreaFraction(PIX *pixs,
472 l_int32 *tab,
473 l_float32 *pfract)
474 {
475 l_int32 w, h, sum;
476 l_int32 *tab8;
477
478 if (!pfract)
479 return ERROR_INT("&fract not defined", __func__, 1);
480 *pfract = 0.0;
481 if (!pixs || pixGetDepth(pixs) != 1)
482 return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1);
483
484 if (!tab)
485 tab8 = makePixelSumTab8();
486 else
487 tab8 = tab;
488 pixGetDimensions(pixs, &w, &h, NULL);
489 pixCountPixels(pixs, &sum, tab8);
490 *pfract = (l_float32)sum / (l_float32)(w * h);
491
492 if (!tab) LEPT_FREE(tab8);
493 return 0;
494 }
495
496
497 /*!
498 * \brief pixaFindAreaFractionMasked()
499 *
500 * \param[in] pixa of 1 bpp pix
501 * \param[in] pixm mask image
502 * \param[in] debug 1 for output, 0 to suppress
503 * \return na of ratio masked/total fractions for each pix,
504 * or NULL on error
505 *
506 * <pre>
507 * Notes:
508 * (1) This is typically used for a pixa consisting of
509 * 1 bpp connected components, which has an associated
510 * boxa giving the location of the components relative
511 * to the mask origin.
512 * (2) The debug flag displays in green and red the masked and
513 * unmasked parts of the image from which pixa was derived.
514 * </pre>
515 */
516 NUMA *
517 pixaFindAreaFractionMasked(PIXA *pixa,
518 PIX *pixm,
519 l_int32 debug)
520 {
521 l_int32 i, n, full;
522 l_int32 *tab;
523 l_float32 fract;
524 BOX *box;
525 NUMA *na;
526 PIX *pix;
527
528 if (!pixa)
529 return (NUMA *)ERROR_PTR("pixa not defined", __func__, NULL);
530 if (!pixm || pixGetDepth(pixm) != 1)
531 return (NUMA *)ERROR_PTR("pixm undefined or not 1 bpp", __func__, NULL);
532
533 n = pixaGetCount(pixa);
534 na = numaCreate(n);
535 tab = makePixelSumTab8();
536 pixaIsFull(pixa, NULL, &full); /* check boxa */
537 box = NULL;
538 for (i = 0; i < n; i++) {
539 pix = pixaGetPix(pixa, i, L_CLONE);
540 if (full)
541 box = pixaGetBox(pixa, i, L_CLONE);
542 pixFindAreaFractionMasked(pix, box, pixm, tab, &fract);
543 numaAddNumber(na, fract);
544 boxDestroy(&box);
545 pixDestroy(&pix);
546 }
547 LEPT_FREE(tab);
548
549 if (debug) {
550 l_int32 w, h;
551 PIX *pix1, *pix2;
552 pixGetDimensions(pixm, &w, &h, NULL);
553 pix1 = pixaDisplay(pixa, w, h); /* recover original image */
554 pix2 = pixCreate(w, h, 8); /* make an 8 bpp white image ... */
555 pixSetColormap(pix2, pixcmapCreate(8)); /* that's cmapped ... */
556 pixSetBlackOrWhite(pix2, L_SET_WHITE); /* and init to white */
557 pixSetMaskedCmap(pix2, pix1, 0, 0, 255, 0, 0); /* color all fg red */
558 pixRasterop(pix1, 0, 0, w, h, PIX_MASK, pixm, 0, 0);
559 pixSetMaskedCmap(pix2, pix1, 0, 0, 0, 255, 0); /* turn masked green */
560 pixDisplay(pix2, 100, 100);
561 pixDestroy(&pix1);
562 pixDestroy(&pix2);
563 }
564
565 return na;
566 }
567
568
569 /*!
570 * \brief pixFindAreaFractionMasked()
571 *
572 * \param[in] pixs 1 bpp, typically a single component
573 * \param[in] box [optional] for pixs relative to pixm
574 * \param[in] pixm 1 bpp mask, typically over the entire image from
575 * which the component pixs was extracted
576 * \param[in] tab [optional] pixel sum table, can be NULL
577 * \param[out] pfract fg area/size ratio
578 * \return 0 if OK, 1 on error
579 *
580 * <pre>
581 * Notes:
582 * (1) This finds the ratio of the number of masked fg pixels
583 * in pixs to the total number of fg pixels in pixs.
584 * It is typically used for a single connected component.
585 * If there are no fg pixels, this returns a ratio of 0.0.
586 * (2) The box gives the location of the pix relative to that
587 * of the UL corner of the mask. Therefore, the rasterop
588 * is performed with the pix translated to its location
589 * (x, y) in the mask before ANDing.
590 * If box == NULL, the UL corners of pixs and pixm are aligned.
591 * </pre>
592 */
593 l_ok
594 pixFindAreaFractionMasked(PIX *pixs,
595 BOX *box,
596 PIX *pixm,
597 l_int32 *tab,
598 l_float32 *pfract)
599 {
600 l_int32 x, y, w, h, sum, masksum;
601 l_int32 *tab8;
602 PIX *pix1;
603
604 if (!pfract)
605 return ERROR_INT("&fract not defined", __func__, 1);
606 *pfract = 0.0;
607 if (!pixs || pixGetDepth(pixs) != 1)
608 return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1);
609 if (!pixm || pixGetDepth(pixm) != 1)
610 return ERROR_INT("pixm not defined or not 1 bpp", __func__, 1);
611
612 if (!tab)
613 tab8 = makePixelSumTab8();
614 else
615 tab8 = tab;
616 x = y = 0;
617 if (box)
618 boxGetGeometry(box, &x, &y, NULL, NULL);
619 pixGetDimensions(pixs, &w, &h, NULL);
620
621 pix1 = pixCopy(NULL, pixs);
622 pixRasterop(pix1, 0, 0, w, h, PIX_MASK, pixm, x, y);
623 pixCountPixels(pixs, &sum, tab8);
624 if (sum == 0) {
625 pixDestroy(&pix1);
626 if (!tab) LEPT_FREE(tab8);
627 return 0;
628 }
629 pixCountPixels(pix1, &masksum, tab8);
630 *pfract = (l_float32)masksum / (l_float32)sum;
631
632 if (!tab) LEPT_FREE(tab8);
633 pixDestroy(&pix1);
634 return 0;
635 }
636
637
638 /*!
639 * \brief pixaFindWidthHeightRatio()
640 *
641 * \param[in] pixa of 1 bpp pix
642 * \return na of width/height ratios for each pix, or NULL on error
643 *
644 * <pre>
645 * Notes:
646 * (1) This is typically used for a pixa consisting of
647 * 1 bpp connected components.
648 * </pre>
649 */
650 NUMA *
651 pixaFindWidthHeightRatio(PIXA *pixa)
652 {
653 l_int32 i, n, w, h;
654 NUMA *na;
655 PIX *pixt;
656
657 if (!pixa)
658 return (NUMA *)ERROR_PTR("pixa not defined", __func__, NULL);
659
660 n = pixaGetCount(pixa);
661 na = numaCreate(n);
662 for (i = 0; i < n; i++) {
663 pixt = pixaGetPix(pixa, i, L_CLONE);
664 pixGetDimensions(pixt, &w, &h, NULL);
665 numaAddNumber(na, (l_float32)w / (l_float32)h);
666 pixDestroy(&pixt);
667 }
668 return na;
669 }
670
671
672 /*!
673 * \brief pixaFindWidthHeightProduct()
674 *
675 * \param[in] pixa of 1 bpp pix
676 * \return na of width*height products for each pix, or NULL on error
677 *
678 * <pre>
679 * Notes:
680 * (1) This is typically used for a pixa consisting of
681 * 1 bpp connected components.
682 * </pre>
683 */
684 NUMA *
685 pixaFindWidthHeightProduct(PIXA *pixa)
686 {
687 l_int32 i, n, w, h;
688 NUMA *na;
689 PIX *pixt;
690
691 if (!pixa)
692 return (NUMA *)ERROR_PTR("pixa not defined", __func__, NULL);
693
694 n = pixaGetCount(pixa);
695 na = numaCreate(n);
696 for (i = 0; i < n; i++) {
697 pixt = pixaGetPix(pixa, i, L_CLONE);
698 pixGetDimensions(pixt, &w, &h, NULL);
699 numaAddNumber(na, w * h);
700 pixDestroy(&pixt);
701 }
702 return na;
703 }
704
705
706 /*!
707 * \brief pixFindOverlapFraction()
708 *
709 * \param[in] pixs1, pixs2 1 bpp
710 * \param[in] x2, y2 location in pixs1 of UL corner of pixs2
711 * \param[in] tab [optional] pixel sum table, can be null
712 * \param[out] pratio ratio fg intersection to fg union
713 * \param[out] pnoverlap [optional] number of overlapping pixels
714 * \return 0 if OK, 1 on error
715 *
716 * <pre>
717 * Notes:
718 * (1) The UL corner of pixs2 is placed at (x2, y2) in pixs1.
719 * (2) This measure is similar to the correlation.
720 * </pre>
721 */
722 l_ok
723 pixFindOverlapFraction(PIX *pixs1,
724 PIX *pixs2,
725 l_int32 x2,
726 l_int32 y2,
727 l_int32 *tab,
728 l_float32 *pratio,
729 l_int32 *pnoverlap)
730 {
731 l_int32 *tab8;
732 l_int32 w, h, nintersect, nunion;
733 PIX *pixt;
734
735 if (pnoverlap) *pnoverlap = 0;
736 if (!pratio)
737 return ERROR_INT("&ratio not defined", __func__, 1);
738 *pratio = 0.0;
739 if (!pixs1 || pixGetDepth(pixs1) != 1)
740 return ERROR_INT("pixs1 not defined or not 1 bpp", __func__, 1);
741 if (!pixs2 || pixGetDepth(pixs2) != 1)
742 return ERROR_INT("pixs2 not defined or not 1 bpp", __func__, 1);
743
744 if (!tab)
745 tab8 = makePixelSumTab8();
746 else
747 tab8 = tab;
748
749 pixGetDimensions(pixs2, &w, &h, NULL);
750 pixt = pixCopy(NULL, pixs1);
751 pixRasterop(pixt, x2, y2, w, h, PIX_MASK, pixs2, 0, 0); /* AND */
752 pixCountPixels(pixt, &nintersect, tab8);
753 if (pnoverlap)
754 *pnoverlap = nintersect;
755 pixCopy(pixt, pixs1);
756 pixRasterop(pixt, x2, y2, w, h, PIX_PAINT, pixs2, 0, 0); /* OR */
757 pixCountPixels(pixt, &nunion, tab8);
758 if (!tab) LEPT_FREE(tab8);
759 pixDestroy(&pixt);
760
761 if (nunion > 0)
762 *pratio = (l_float32)nintersect / (l_float32)nunion;
763 return 0;
764 }
765
766
767 /*!
768 * \brief pixFindRectangleComps()
769 *
770 * \param[in] pixs 1 bpp
771 * \param[in] dist max distance allowed between bounding box
772 * and nearest foreground pixel within it
773 * \param[in] minw, minh minimum size in each direction as a requirement
774 * for a conforming rectangle
775 * \return boxa of components that conform, or NULL on error
776 *
777 * <pre>
778 * Notes:
779 * (1) This applies the function pixConformsToRectangle() to
780 * each 8-c.c. in pixs, and returns a boxa containing the
781 * regions of all components that are conforming.
782 * (2) Conforming components must satisfy both the size constraint
783 * given by %minsize and the slop in conforming to a rectangle
784 * determined by %dist.
785 * </pre>
786 */
787 BOXA *
788 pixFindRectangleComps(PIX *pixs,
789 l_int32 dist,
790 l_int32 minw,
791 l_int32 minh)
792 {
793 l_int32 w, h, i, n, conforms;
794 BOX *box;
795 BOXA *boxa, *boxad;
796 PIX *pix;
797 PIXA *pixa;
798
799 if (!pixs || pixGetDepth(pixs) != 1)
800 return (BOXA *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL);
801 if (dist < 0)
802 return (BOXA *)ERROR_PTR("dist must be >= 0", __func__, NULL);
803 if (minw <= 2 * dist && minh <= 2 * dist)
804 return (BOXA *)ERROR_PTR("invalid parameters", __func__, NULL);
805
806 boxa = pixConnComp(pixs, &pixa, 8);
807 boxad = boxaCreate(0);
808 n = pixaGetCount(pixa);
809 for (i = 0; i < n; i++) {
810 pix = pixaGetPix(pixa, i, L_CLONE);
811 pixGetDimensions(pix, &w, &h, NULL);
812 if (w < minw || h < minh) {
813 pixDestroy(&pix);
814 continue;
815 }
816 pixConformsToRectangle(pix, NULL, dist, &conforms);
817 if (conforms) {
818 box = boxaGetBox(boxa, i, L_COPY);
819 boxaAddBox(boxad, box, L_INSERT);
820 }
821 pixDestroy(&pix);
822 }
823 boxaDestroy(&boxa);
824 pixaDestroy(&pixa);
825 return boxad;
826 }
827
828
829 /*!
830 * \brief pixConformsToRectangle()
831 *
832 * \param[in] pixs 1 bpp
833 * \param[in] box [optional] if null, use the entire pixs
834 * \param[in] dist max distance allowed between bounding box and
835 * nearest foreground pixel within it
836 * \param[out] pconforms 0 (false) if not conforming;
837 * 1 (true) if conforming
838 * \return 0 if OK, 1 on error
839 *
840 * <pre>
841 * Notes:
842 * (1) There are several ways to test if a connected component has
843 * an essentially rectangular boundary, such as:
844 * a. Fraction of fill into the bounding box
845 * b. Max-min distance of fg pixel from periphery of bounding box
846 * c. Max depth of bg intrusions into component within bounding box
847 * The weakness of (a) is that it is highly sensitive to holes
848 * within the c.c. The weakness of (b) is that it can have
849 * arbitrarily large intrusions into the c.c. Method (c) tests
850 * the integrity of the outer boundary of the c.c., with respect
851 * to the enclosing bounding box, so we use it.
852 * (2) This tests if the connected component within the box conforms
853 * to the box at all points on the periphery within %dist.
854 * Inside, at a distance from the box boundary that is greater
855 * than %dist, we don't care about the pixels in the c.c.
856 * (3) We can think of the conforming condition as follows:
857 * No pixel inside a distance %dist from the boundary
858 * can connect to the boundary through a path through the bg.
859 * To implement this, we need to do a flood fill. We can go
860 * either from inside toward the boundary, or the other direction.
861 * It's easiest to fill from the boundary, and then verify that
862 * there are no filled pixels farther than %dist from the boundary.
863 * </pre>
864 */
865 l_ok
866 pixConformsToRectangle(PIX *pixs,
867 BOX *box,
868 l_int32 dist,
869 l_int32 *pconforms)
870 {
871 l_int32 w, h, empty;
872 PIX *pix1, *pix2;
873
874 if (!pconforms)
875 return ERROR_INT("&conforms not defined", __func__, 1);
876 *pconforms = 0;
877 if (!pixs || pixGetDepth(pixs) != 1)
878 return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1);
879 if (dist < 0)
880 return ERROR_INT("dist must be >= 0", __func__, 1);
881 pixGetDimensions(pixs, &w, &h, NULL);
882 if (w <= 2 * dist || h <= 2 * dist) {
883 L_WARNING("automatic conformation: distance too large\n", __func__);
884 *pconforms = 1;
885 return 0;
886 }
887
888 /* Extract the region, if necessary */
889 if (box)
890 pix1 = pixClipRectangle(pixs, box, NULL);
891 else
892 pix1 = pixCopy(NULL, pixs);
893
894 /* Invert and fill from the boundary into the interior.
895 * Because we're considering the connected component in an
896 * 8-connected sense, we do the background filling as 4 c.c. */
897 pixInvert(pix1, pix1);
898 pix2 = pixExtractBorderConnComps(pix1, 4);
899
900 /* Mask out all pixels within a distance %dist from the box
901 * boundary. Any remaining pixels are from filling that goes
902 * more than %dist from the boundary. If no pixels remain,
903 * the component conforms to the bounding rectangle within
904 * a distance %dist. */
905 pixSetOrClearBorder(pix2, dist, dist, dist, dist, PIX_CLR);
906 pixZero(pix2, &empty);
907 pixDestroy(&pix1);
908 pixDestroy(&pix2);
909 *pconforms = (empty) ? 1 : 0;
910 return 0;
911 }
912
913
914 /*-----------------------------------------------------------------------*
915 * Extract rectangular regions *
916 *-----------------------------------------------------------------------*/
917 /*!
918 * \brief pixExtractRectangularRegions()
919 *
920 * \param[in] pixs
921 * \param[in] boxa regions to extract
922 * \return pix with extracted regions, or NULL on error
923 *
924 * <pre>
925 * Notes:
926 * (1) The returned pix has the rectangular regions clipped from
927 * the input pixs.
928 * (2) We could equally well do this operation using a mask of 1's over
929 * the regions determined by the boxa:
930 * pix1 = pixCreateTemplate(pixs);
931 * pixMaskBoxa(pix1, pix1, boxa, L_SET_PIXELS);
932 * pixAnd(pix1, pix1, pixs);
933 * </pre>
934 */
935 PIX *
936 pixExtractRectangularRegions(PIX *pixs,
937 BOXA *boxa)
938 {
939 l_int32 w, h;
940 PIX *pix1;
941 PIXA *pixa1;
942
943 if (!pixs)
944 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
945 if (!boxa)
946 return (PIX *)ERROR_PTR("boxa not defined", __func__, NULL);
947
948 if ((pixa1 = pixClipRectangles(pixs, boxa)) == NULL)
949 return (PIX *)ERROR_PTR("pixa1 not made", __func__, NULL);
950 pixGetDimensions(pixs, &w, &h, NULL);
951 pix1 = pixaDisplay(pixa1, w, h);
952 pixaDestroy(&pixa1);
953 return pix1;
954 }
955
956
957 /*!
958 * \brief pixClipRectangles()
959 *
960 * \param[in] pixs
961 * \param[in] boxa requested clipping regions
962 * \return pixa consisting of requested regions, or NULL on error
963 *
964 * <pre>
965 * Notes:
966 * (1) The boxa in the returned pixa has the regions clipped from
967 * the input pixs.
968 * </pre>
969 */
970 PIXA *
971 pixClipRectangles(PIX *pixs,
972 BOXA *boxa)
973 {
974 l_int32 i, n;
975 BOX *box, *boxc;
976 PIX *pix;
977 PIXA *pixa;
978
979 if (!pixs)
980 return (PIXA *)ERROR_PTR("pixs not defined", __func__, NULL);
981 if (!boxa)
982 return (PIXA *)ERROR_PTR("boxa not defined", __func__, NULL);
983
984 n = boxaGetCount(boxa);
985 pixa = pixaCreate(n);
986 for (i = 0; i < n; i++) {
987 box = boxaGetBox(boxa, i, L_CLONE);
988 pix = pixClipRectangle(pixs, box, &boxc);
989 pixaAddPix(pixa, pix, L_INSERT);
990 pixaAddBox(pixa, boxc, L_INSERT);
991 boxDestroy(&box);
992 }
993
994 return pixa;
995 }
996
997
998 /*!
999 * \brief pixClipRectangle()
1000 *
1001 * \param[in] pixs
1002 * \param[in] box requested clipping region; const
1003 * \param[out] pboxc [optional] actual box of clipped region
1004 * \return clipped pix, or NULL on error or if rectangle
1005 * doesn't intersect pixs
1006 *
1007 * <pre>
1008 * Notes:
1009 *
1010 * This should be simple, but there are choices to be made.
1011 * The box is defined relative to the pix coordinates. However,
1012 * if the box is not contained within the pix, we have two choices:
1013 *
1014 * (1) clip the box to the pix
1015 * (2) make a new pix equal to the full box dimensions,
1016 * but let rasterop do the clipping and positioning
1017 * of the src with respect to the dest
1018 *
1019 * Choice (2) immediately brings up the problem of what pixel values
1020 * to use that were not taken from the src. For example, on a grayscale
1021 * image, do you want the pixels not taken from the src to be black
1022 * or white or something else? To implement choice 2, one needs to
1023 * specify the color of these extra pixels.
1024 *
1025 * So we adopt (1), and clip the box first, if necessary,
1026 * before making the dest pix and doing the rasterop. But there
1027 * is another issue to consider. If you want to paste the
1028 * clipped pix back into pixs, it must be properly aligned, and
1029 * it is necessary to use the clipped box for alignment.
1030 * Accordingly, this function has a third (optional) argument, which is
1031 * the input box clipped to the src pix.
1032 * </pre>
1033 */
1034 PIX *
1035 pixClipRectangle(PIX *pixs,
1036 BOX *box,
1037 BOX **pboxc)
1038 {
1039 l_int32 w, h, d, bx, by, bw, bh;
1040 BOX *boxc;
1041 PIX *pixd;
1042
1043 if (pboxc) *pboxc = NULL;
1044 if (!pixs)
1045 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
1046 if (!box)
1047 return (PIX *)ERROR_PTR("box not defined", __func__, NULL);
1048
1049 /* Clip the input box to the pix */
1050 pixGetDimensions(pixs, &w, &h, &d);
1051 if ((boxc = boxClipToRectangle(box, w, h)) == NULL) {
1052 L_WARNING("box doesn't overlap pix\n", __func__);
1053 return NULL;
1054 }
1055 boxGetGeometry(boxc, &bx, &by, &bw, &bh);
1056
1057 /* Extract the block */
1058 if ((pixd = pixCreate(bw, bh, d)) == NULL) {
1059 boxDestroy(&boxc);
1060 return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
1061 }
1062 pixCopyResolution(pixd, pixs);
1063 pixCopyColormap(pixd, pixs);
1064 pixCopyText(pixd, pixs);
1065 pixRasterop(pixd, 0, 0, bw, bh, PIX_SRC, pixs, bx, by);
1066
1067 if (pboxc)
1068 *pboxc = boxc;
1069 else
1070 boxDestroy(&boxc);
1071
1072 return pixd;
1073 }
1074
1075
1076 /*!
1077 * \brief pixClipRectangleWithBorder()
1078 *
1079 * \param[in] pixs
1080 * \param[in] box requested clipping region; const
1081 * \param[in] maxbord maximum amount of border to include
1082 * \param[out] pboxn box in coordinates of returned pix
1083 * \return under-clipped pix, or NULL on error or if rectangle
1084 * doesn't intersect pixs
1085 *
1086 * <pre>
1087 * Notes:
1088 * (1) This underclips by an amount determined by the minimum of
1089 * %maxbord and the amount of border that can be included
1090 * equally on all 4 sides.
1091 * (2) If part of the rectangle lies outside the pix, no border
1092 * is included on any side.
1093 * </pre>
1094 */
1095 PIX *
1096 pixClipRectangleWithBorder(PIX *pixs,
1097 BOX *box,
1098 l_int32 maxbord,
1099 BOX **pboxn)
1100 {
1101 l_int32 w, h, bx, by, bw, bh, bord;
1102 BOX *box1;
1103 PIX *pix1;
1104
1105 if (!pboxn)
1106 return (PIX *)ERROR_PTR("&boxn not defined", __func__, NULL);
1107 *pboxn = NULL;
1108 if (!pixs)
1109 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
1110 if (!box)
1111 return (PIX *)ERROR_PTR("box not defined", __func__, NULL);
1112
1113 /* Determine the border width */
1114 pixGetDimensions(pixs, &w, &h, NULL);
1115 boxGetGeometry(box, &bx, &by, &bw, &bh);
1116 bord = L_MIN(bx, by);
1117 bord = L_MIN(bord, w - bx - bw);
1118 bord = L_MIN(bord, h - by - bh);
1119 bord = L_MIN(bord, maxbord);
1120
1121 if (bord <= 0) { /* standard clipping */
1122 pix1 = pixClipRectangle(pixs, box, NULL);
1123 pixGetDimensions(pix1, &w, &h, NULL);
1124 *pboxn = boxCreate(0, 0, w, h);
1125 return pix1;
1126 }
1127
1128 /* There is a positive border */
1129 box1 = boxAdjustSides(NULL, box, -bord, bord, -bord, bord);
1130 pix1 = pixClipRectangle(pixs, box1, NULL);
1131 boxDestroy(&box1);
1132 *pboxn = boxCreate(bord, bord, bw, bh);
1133 return pix1;
1134 }
1135
1136
1137 /*!
1138 * \brief pixClipMasked()
1139 *
1140 * \param[in] pixs 1, 2, 4, 8, 16, 32 bpp; colormap ok
1141 * \param[in] pixm clipping mask, 1 bpp
1142 * \param[in] x, y origin of clipping mask relative to pixs
1143 * \param[in] outval val to use for pixels that are outside the mask
1144 * \return pixd, clipped pix or NULL on error or if pixm doesn't
1145 * intersect pixs
1146 *
1147 * <pre>
1148 * Notes:
1149 * (1) If pixs has a colormap, it is preserved in pixd.
1150 * (2) The depth of pixd is the same as that of pixs.
1151 * (3) If the depth of pixs is 1, use %outval = 0 for white background
1152 * and 1 for black; otherwise, use the max value for white
1153 * and 0 for black. If pixs has a colormap, the max value for
1154 * %outval is 0xffffffff; otherwise, it is 2^d - 1.
1155 * (4) When using 1 bpp pixs, this is a simple clip and
1156 * blend operation. For example, if both pix1 and pix2 are
1157 * black text on white background, and you want to OR the
1158 * fg on the two images, let pixm be the inverse of pix2.
1159 * Then the operation takes all of pix1 that's in the bg of
1160 * pix2, and for the remainder (which are the pixels
1161 * corresponding to the fg of the pix2), paint them black
1162 * (1) in pix1. The function call looks like
1163 * pixClipMasked(pix2, pixInvert(pix1, pix1), x, y, 1);
1164 * </pre>
1165 */
1166 PIX *
1167 pixClipMasked(PIX *pixs,
1168 PIX *pixm,
1169 l_int32 x,
1170 l_int32 y,
1171 l_uint32 outval)
1172 {
1173 l_int32 wm, hm, index, rval, gval, bval;
1174 l_uint32 pixel;
1175 BOX *box;
1176 PIX *pixmi, *pixd;
1177 PIXCMAP *cmap;
1178
1179 if (!pixs)
1180 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
1181 if (!pixm || pixGetDepth(pixm) != 1)
1182 return (PIX *)ERROR_PTR("pixm undefined or not 1 bpp", __func__, NULL);
1183
1184 /* Clip out the region specified by pixm and (x,y) */
1185 pixGetDimensions(pixm, &wm, &hm, NULL);
1186 box = boxCreate(x, y, wm, hm);
1187 pixd = pixClipRectangle(pixs, box, NULL);
1188
1189 /* Paint 'outval' (or something close to it if cmapped) through
1190 * the pixels not masked by pixm */
1191 cmap = pixGetColormap(pixd);
1192 pixmi = pixInvert(NULL, pixm);
1193 if (cmap) {
1194 extractRGBValues(outval, &rval, &gval, &bval);
1195 pixcmapGetNearestIndex(cmap, rval, gval, bval, &index);
1196 pixcmapGetColor(cmap, index, &rval, &gval, &bval);
1197 composeRGBPixel(rval, gval, bval, &pixel);
1198 pixPaintThroughMask(pixd, pixmi, 0, 0, pixel);
1199 } else {
1200 pixPaintThroughMask(pixd, pixmi, 0, 0, outval);
1201 }
1202
1203 boxDestroy(&box);
1204 pixDestroy(&pixmi);
1205 return pixd;
1206 }
1207
1208
1209 /*!
1210 * \brief pixCropToMatch()
1211 *
1212 * \param[in] pixs1 any depth, colormap OK
1213 * \param[in] pixs2 any depth, colormap OK
1214 * \param[out] ppixd1 may be a clone
1215 * \param[out] ppixd2 may be a clone
1216 * \return 0 if OK, 1 on error
1217 *
1218 * <pre>
1219 * Notes:
1220 * (1) This resizes pixs1 and/or pixs2 by cropping at the right
1221 * and bottom, so that they're the same size.
1222 * (2) If a pix doesn't need to be cropped, a clone is returned.
1223 * (3) Note: the images are implicitly aligned to the UL corner.
1224 * </pre>
1225 */
1226 l_ok
1227 pixCropToMatch(PIX *pixs1,
1228 PIX *pixs2,
1229 PIX **ppixd1,
1230 PIX **ppixd2)
1231 {
1232 l_int32 w1, h1, w2, h2, w, h;
1233
1234 if (!ppixd1 || !ppixd2)
1235 return ERROR_INT("&pixd1 and &pixd2 not both defined", __func__, 1);
1236 *ppixd1 = *ppixd2 = NULL;
1237 if (!pixs1 || !pixs2)
1238 return ERROR_INT("pixs1 and pixs2 not defined", __func__, 1);
1239
1240 pixGetDimensions(pixs1, &w1, &h1, NULL);
1241 pixGetDimensions(pixs2, &w2, &h2, NULL);
1242 w = L_MIN(w1, w2);
1243 h = L_MIN(h1, h2);
1244
1245 *ppixd1 = pixCropToSize(pixs1, w, h);
1246 *ppixd2 = pixCropToSize(pixs2, w, h);
1247 if (*ppixd1 == NULL || *ppixd2 == NULL)
1248 return ERROR_INT("cropped image failure", __func__, 1);
1249 return 0;
1250 }
1251
1252
1253 /*!
1254 * \brief pixCropToSize()
1255 *
1256 * \param[in] pixs any depth, colormap OK
1257 * \param[in] w, h max dimensions of cropped image
1258 * \return pixd cropped if necessary or NULL on error.
1259 *
1260 * <pre>
1261 * Notes:
1262 * (1) If either w or h is smaller than the corresponding dimension
1263 * of pixs, this returns a cropped image; otherwise it returns
1264 * a clone of pixs.
1265 * </pre>
1266 */
1267 PIX *
1268 pixCropToSize(PIX *pixs,
1269 l_int32 w,
1270 l_int32 h)
1271 {
1272 l_int32 ws, hs, wd, hd, d;
1273 PIX *pixd;
1274
1275 if (!pixs)
1276 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
1277
1278 pixGetDimensions(pixs, &ws, &hs, &d);
1279 if (ws <= w && hs <= h) /* no cropping necessary */
1280 return pixClone(pixs);
1281
1282 wd = L_MIN(ws, w);
1283 hd = L_MIN(hs, h);
1284 if ((pixd = pixCreate(wd, hd, d)) == NULL)
1285 return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
1286 pixCopyResolution(pixd, pixs);
1287 pixCopyColormap(pixd, pixs);
1288 pixCopyText(pixd, pixs);
1289 pixCopyInputFormat(pixd, pixs);
1290 pixRasterop(pixd, 0, 0, wd, hd, PIX_SRC, pixs, 0, 0);
1291 return pixd;
1292 }
1293
1294
1295 /*!
1296 * \brief pixResizeToMatch()
1297 *
1298 * \param[in] pixs 1, 2, 4, 8, 16, 32 bpp; colormap ok
1299 * \param[in] pixt can be null; we use only the size
1300 * \param[in] w, h ignored if pixt is defined
1301 * \return pixd resized to match or NULL on error
1302 *
1303 * <pre>
1304 * Notes:
1305 * (1) This resizes pixs to make pixd, without scaling, by either
1306 * cropping or extending separately in both width and height.
1307 * Extension is done by replicating the last row or column.
1308 * This is useful in a situation where, due to scaling
1309 * operations, two images that are expected to be the
1310 * same size can differ slightly in each dimension.
1311 * (2) You can use either an existing pixt or specify
1312 * both %w and %h. If pixt is defined, the values
1313 * in %w and %h are ignored.
1314 * (3) If pixt is larger than pixs (or if w and/or d is larger
1315 * than the dimension of pixs, replicate the outer row and
1316 * column of pixels in pixs into pixd.
1317 * </pre>
1318 */
1319 PIX *
1320 pixResizeToMatch(PIX *pixs,
1321 PIX *pixt,
1322 l_int32 w,
1323 l_int32 h)
1324 {
1325 l_int32 i, j, ws, hs, d;
1326 PIX *pixd;
1327
1328 if (!pixs)
1329 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
1330 if (!pixt && (w <= 0 || h <= 0))
1331 return (PIX *)ERROR_PTR("both w and h not > 0", __func__, NULL);
1332
1333 if (pixt) /* redefine w, h */
1334 pixGetDimensions(pixt, &w, &h, NULL);
1335 pixGetDimensions(pixs, &ws, &hs, &d);
1336 if (ws == w && hs == h)
1337 return pixCopy(NULL, pixs);
1338
1339 if ((pixd = pixCreate(w, h, d)) == NULL)
1340 return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
1341 pixCopyResolution(pixd, pixs);
1342 pixCopyColormap(pixd, pixs);
1343 pixCopyText(pixd, pixs);
1344 pixCopyInputFormat(pixd, pixs);
1345 pixRasterop(pixd, 0, 0, ws, hs, PIX_SRC, pixs, 0, 0);
1346 if (ws >= w && hs >= h)
1347 return pixd;
1348
1349 /* Replicate the last column and then the last row */
1350 if (ws < w) {
1351 for (j = ws; j < w; j++)
1352 pixRasterop(pixd, j, 0, 1, h, PIX_SRC, pixd, ws - 1, 0);
1353 }
1354 if (hs < h) {
1355 for (i = hs; i < h; i++)
1356 pixRasterop(pixd, 0, i, w, 1, PIX_SRC, pixd, 0, hs - 1);
1357 }
1358
1359 return pixd;
1360 }
1361
1362
1363 /*---------------------------------------------------------------------*
1364 * Select a connected component by size *
1365 *---------------------------------------------------------------------*/
1366 /*!
1367 * \brief pixSelectComponentBySize()
1368 *
1369 * \param[in] pixs 1 bpp
1370 * \param[in] rankorder in decreasing size: 0 for largest.
1371 * \param[in] type L_SELECT_BY_WIDTH, L_SELECT_BY_HEIGHT,
1372 * L_SELECT_BY_MAX_DIMENSION,
1373 * L_SELECT_BY_AREA, L_SELECT_BY_PERIMETER
1374 * \param[in] connectivity 4 or 8
1375 * \param[out] pbox [optional] location of returned component
1376 * \return pix of rank order connected component, or NULL on error.
1377 *
1378 * <pre>
1379 * Notes:
1380 * (1) This selects the Nth largest connected component, based on
1381 * the selection type and connectivity.
1382 * (2) Note that %rankorder is an integer. Use %rankorder = 0 for
1383 * the largest component and %rankorder = -1 for the smallest.
1384 * If %rankorder >= number of components, select the smallest.
1385 */
1386 PIX *
1387 pixSelectComponentBySize(PIX *pixs,
1388 l_int32 rankorder,
1389 l_int32 type,
1390 l_int32 connectivity,
1391 BOX **pbox)
1392 {
1393 l_int32 n, empty, sorttype, index;
1394 BOXA *boxa1;
1395 NUMA *naindex;
1396 PIX *pixd;
1397 PIXA *pixa1, *pixa2;
1398
1399 if (pbox) *pbox = NULL;
1400 if (!pixs || pixGetDepth(pixs) != 1)
1401 return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL);
1402 if (type == L_SELECT_BY_WIDTH)
1403 sorttype = L_SORT_BY_WIDTH;
1404 else if (type == L_SELECT_BY_HEIGHT)
1405 sorttype = L_SORT_BY_HEIGHT;
1406 else if (type == L_SELECT_BY_MAX_DIMENSION)
1407 sorttype = L_SORT_BY_MAX_DIMENSION;
1408 else if (type == L_SELECT_BY_AREA)
1409 sorttype = L_SORT_BY_AREA;
1410 else if (type == L_SELECT_BY_PERIMETER)
1411 sorttype = L_SORT_BY_PERIMETER;
1412 else
1413 return (PIX *)ERROR_PTR("invalid selection type", __func__, NULL);
1414 if (connectivity != 4 && connectivity != 8)
1415 return (PIX *)ERROR_PTR("connectivity not 4 or 8", __func__, NULL);
1416 pixZero(pixs, &empty);
1417 if (empty)
1418 return (PIX *)ERROR_PTR("no foreground pixels", __func__, NULL);
1419
1420 boxa1 = pixConnComp(pixs, &pixa1, connectivity);
1421 n = boxaGetCount(boxa1);
1422 if (rankorder < 0 || rankorder >= n)
1423 rankorder = n - 1; /* smallest */
1424 pixa2 = pixaSort(pixa1, sorttype, L_SORT_DECREASING, &naindex, L_CLONE);
1425 pixd = pixaGetPix(pixa2, rankorder, L_COPY);
1426 if (pbox) {
1427 numaGetIValue(naindex, rankorder, &index);
1428 *pbox = boxaGetBox(boxa1, index, L_COPY);
1429 }
1430
1431 numaDestroy(&naindex);
1432 boxaDestroy(&boxa1);
1433 pixaDestroy(&pixa1);
1434 pixaDestroy(&pixa2);
1435 return pixd;
1436 }
1437
1438
1439 /*!
1440 * \brief pixFilterComponentBySize()
1441 *
1442 * \param[in] pixs 1 bpp
1443 * \param[in] rankorder in decreasing size: 0 for largest.
1444 * \param[in] type L_SELECT_BY_WIDTH, L_SELECT_BY_HEIGHT,
1445 * L_SELECT_BY_MAX_DIMENSION,
1446 * L_SELECT_BY_AREA, L_SELECT_BY_PERIMETER
1447 * \param[in] connectivity 4 or 8
1448 * \param[out] pbox [optional] location of returned component
1449 * \return pix with all other components removed, or NULL on error.
1450 *
1451 * <pre>
1452 * Notes:
1453 * (1) See notes in pixSelectComponentBySize().
1454 * (2) This returns a copy of %pixs, with all components removed
1455 * except for the selected one.
1456 */
1457 PIX *
1458 pixFilterComponentBySize(PIX *pixs,
1459 l_int32 rankorder,
1460 l_int32 type,
1461 l_int32 connectivity,
1462 BOX **pbox)
1463 {
1464 l_int32 x, y, w, h;
1465 BOX *box;
1466 PIX *pix1, *pix2;
1467
1468 if (!pixs || pixGetDepth(pixs) != 1)
1469 return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL);
1470
1471 pix1 = pixSelectComponentBySize(pixs, rankorder, type, connectivity, &box);
1472 if (!pix1) {
1473 boxDestroy(&box);
1474 return (PIX *)ERROR_PTR("pix1 not made", __func__, NULL);
1475 }
1476
1477 /* Put the selected component in a new pix at the same
1478 * location as it had in %pixs */
1479 boxGetGeometry(box, &x, &y, &w, &h);
1480 pix2 = pixCreateTemplate(pixs);
1481 pixRasterop(pix2, x, y, w, h, PIX_SRC, pix1, 0, 0);
1482 if (pbox)
1483 *pbox = box;
1484 else
1485 boxDestroy(&box);
1486 pixDestroy(&pix1);
1487 return pix2;
1488 }
1489
1490
1491 /*---------------------------------------------------------------------*
1492 * Make special masks *
1493 *---------------------------------------------------------------------*/
1494 /*!
1495 * \brief pixMakeSymmetricMask()
1496 *
1497 * \param[in] w, h dimensions of output 1 bpp pix
1498 * \param[in] hf horizontal fraction of half-width
1499 * \param[in] vf vertical fraction of half-height
1500 * \param[in] type L_USE_INNER, L_USE_OUTER
1501 * \return pixd 1 bpp, or NULL on error.
1502 *
1503 * <pre>
1504 * Notes:
1505 * (1) This is a convenience function for generating masks with
1506 * horizontal and vertical reflection symmetry, over either
1507 * the inner or outer parts of an image.
1508 * (2) Using L_USE_INNER to generate a mask over the inner part
1509 * of the image, the mask is a solid rectangle, and the fractions
1510 * describe the distance between the boundary of the image and
1511 * the rectangle boundary. For example, with hf == vf == 0.0,
1512 * the mask covers the full image.
1513 * (3) Using L_USE_OUTER to generate a mask over an outer frame
1514 * of the image, the mask touches the boundary of the image,
1515 * and the fractions describe the location of the inner
1516 * boundary of the frame. For example, with hf == vf == 1.0,
1517 * the inner boundary is at the center of the image, so the
1518 * mask covers the full image.
1519 * (4) More examples:
1520 * * mask covering the inner 70%: hf = vf = 0.3, type = L_USE_INNER
1521 * * frame covering the outer 30%: hf = vf = 0.3, type = L_USE_OUTER
1522 * </pre>
1523 */
1524 PIX *
1525 pixMakeSymmetricMask(l_int32 w,
1526 l_int32 h,
1527 l_float32 hf,
1528 l_float32 vf,
1529 l_int32 type)
1530 {
1531 if (w <= 0 || h <= 0)
1532 return (PIX *)ERROR_PTR("mask size 0", __func__, NULL);
1533 if (hf < 0.0 || hf > 1.0)
1534 return (PIX *)ERROR_PTR("invalid horiz fractions", __func__, NULL);
1535 if (vf < 0.0 || vf > 1.0)
1536 return (PIX *)ERROR_PTR("invalid vert fractions", __func__, NULL);
1537
1538 if (type == L_USE_INNER)
1539 return pixMakeFrameMask(w, h, hf, 1.0, vf, 1.0);
1540 else if (type == L_USE_OUTER)
1541 return pixMakeFrameMask(w, h, 0.0, hf, 0.0, vf);
1542 else
1543 return (PIX *)ERROR_PTR("invalid type", __func__, NULL);
1544 }
1545
1546
1547 /*!
1548 * \brief pixMakeFrameMask()
1549 *
1550 * \param[in] w, h dimensions of output 1 bpp pix
1551 * \param[in] hf1 horizontal fraction of half-width at outer frame bdry
1552 * \param[in] hf2 horizontal fraction of half-width at inner frame bdry
1553 * \param[in] vf1 vertical fraction of half-width at outer frame bdry
1554 * \param[in] vf2 vertical fraction of half-width at inner frame bdry
1555 * \return pixd 1 bpp, or NULL on error.
1556 *
1557 * <pre>
1558 * Notes:
1559 * (1) This makes an arbitrary 1-component mask with a centered fg
1560 * frame, which can have both an inner and an outer boundary.
1561 * All input fractional distances are measured from the image
1562 * border to the frame boundary, in units of the image half-width
1563 * for hf1 and hf2 and the image half-height for vf1 and vf2.
1564 * The distances to the outer frame boundary are given by hf1
1565 * and vf1; to the inner frame boundary, by hf2 and vf2.
1566 * Input fractions are thus in [0.0 ... 1.0], with hf1 <= hf2
1567 * and vf1 <= vf2. Horizontal and vertical frame widths are
1568 * thus independently specified.
1569 * (2) Special cases:
1570 * * full fg mask: hf1 = vf1 = 0.0, hf2 = vf2 = 1.0.
1571 * * empty fg (zero width) mask: set hf1 = hf2 and vf1 = vf2.
1572 * * fg rectangle with no hole: set hf2 = vf2 = 1.0.
1573 * * frame touching outer boundary: set hf1 = vf1 = 0.0.
1574 * (3) The vertical thickness of the horizontal mask parts
1575 * is 0.5 * (vf2 - vf1) * h. The horizontal thickness of the
1576 * vertical mask parts is 0.5 * (hf2 - hf1) * w.
1577 * </pre>
1578 */
1579 PIX *
1580 pixMakeFrameMask(l_int32 w,
1581 l_int32 h,
1582 l_float32 hf1,
1583 l_float32 hf2,
1584 l_float32 vf1,
1585 l_float32 vf2)
1586 {
1587 l_int32 h1, h2, v1, v2;
1588 PIX *pixd;
1589
1590 if (w <= 0 || h <= 0)
1591 return (PIX *)ERROR_PTR("mask size 0", __func__, NULL);
1592 if (hf1 < 0.0 || hf1 > 1.0 || hf2 < 0.0 || hf2 > 1.0)
1593 return (PIX *)ERROR_PTR("invalid horiz fractions", __func__, NULL);
1594 if (vf1 < 0.0 || vf1 > 1.0 || vf2 < 0.0 || vf2 > 1.0)
1595 return (PIX *)ERROR_PTR("invalid vert fractions", __func__, NULL);
1596 if (hf1 > hf2 || vf1 > vf2)
1597 return (PIX *)ERROR_PTR("invalid relative sizes", __func__, NULL);
1598
1599 pixd = pixCreate(w, h, 1);
1600
1601 /* Special cases */
1602 if (hf1 == 0.0 && vf1 == 0.0 && hf2 == 1.0 && vf2 == 1.0) { /* full */
1603 pixSetAll(pixd);
1604 return pixd;
1605 }
1606 if (hf1 == hf2 && vf1 == vf2) { /* empty */
1607 return pixd;
1608 }
1609
1610 /* General case */
1611 h1 = 0.5f * hf1 * w;
1612 h2 = 0.5f * hf2 * w;
1613 v1 = 0.5f * vf1 * h;
1614 v2 = 0.5f * vf2 * h;
1615 pixRasterop(pixd, h1, v1, w - 2 * h1, h - 2 * v1, PIX_SET, NULL, 0, 0);
1616 if (hf2 < 1.0 && vf2 < 1.0)
1617 pixRasterop(pixd, h2, v2, w - 2 * h2, h - 2 * v2, PIX_CLR, NULL, 0, 0);
1618 return pixd;
1619 }
1620
1621
1622 /*---------------------------------------------------------------------*
1623 * Generate a covering of rectangles over connected components *
1624 *---------------------------------------------------------------------*/
1625 /*!
1626 * \brief pixMakeCoveringOfRectangles()
1627 *
1628 * \param[in] pixs 1 bpp
1629 * \param[in] maxiters max iterations: use 0 to iterate to completion
1630 * \return pixd, or NULL on error
1631 *
1632 * <pre>
1633 * Notes:
1634 * (1) This iteratively finds the bounding boxes of the connected
1635 * components and generates a mask from them. Two iterations
1636 * should suffice for most situations.
1637 * (2) Returns an empty pix if %pixs is empty.
1638 * (3) If there are many small components in proximity, it may
1639 * be useful to merge them with a morphological closing before
1640 * calling this one.
1641 * </pre>
1642 */
1643 PIX *
1644 pixMakeCoveringOfRectangles(PIX *pixs,
1645 l_int32 maxiters)
1646 {
1647 l_int32 empty, same, niters;
1648 BOXA *boxa;
1649 PIX *pix1, *pix2;
1650
1651 if (!pixs || pixGetDepth(pixs) != 1)
1652 return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL);
1653 if (maxiters < 0)
1654 return (PIX *)ERROR_PTR("maxiters must be >= 0", __func__, NULL);
1655 if (maxiters == 0) maxiters = 50; /* ridiculously large number */
1656
1657 pixZero(pixs, &empty);
1658 pix1 = pixCreateTemplate(pixs);
1659 if (empty) return pix1;
1660
1661 /* Do first iteration */
1662 boxa = pixConnCompBB(pixs, 8);
1663 pixMaskBoxa(pix1, pix1, boxa, L_SET_PIXELS);
1664 boxaDestroy(&boxa);
1665 if (maxiters == 1) return pix1;
1666
1667 niters = 1;
1668 while (niters < maxiters) { /* continue to add pixels to pix1 */
1669 niters++;
1670 boxa = pixConnCompBB(pix1, 8);
1671 pix2 = pixCopy(NULL, pix1);
1672 pixMaskBoxa(pix1, pix1, boxa, L_SET_PIXELS);
1673 boxaDestroy(&boxa);
1674 pixEqual(pix1, pix2, &same);
1675 pixDestroy(&pix2);
1676 if (same) {
1677 L_INFO("%d iterations\n", __func__, niters - 1);
1678 return pix1;
1679 }
1680 }
1681 L_INFO("maxiters = %d reached\n", __func__, niters);
1682 return pix1;
1683 }
1684
1685
1686 /*---------------------------------------------------------------------*
1687 * Fraction of Fg pixels under a mask *
1688 *---------------------------------------------------------------------*/
1689 /*!
1690 * \brief pixFractionFgInMask()
1691 *
1692 * \param[in] pix1 1 bpp
1693 * \param[in] pix2 1 bpp
1694 * \param[out] pfract fraction of fg pixels in 1 that are
1695 * aligned with the fg of 2
1696 * \return 0 if OK, 1 on error.
1697 *
1698 * <pre>
1699 * Notes:
1700 * (1) This gives the fraction of fg pixels in pix1 that are in
1701 * the intersection (i.e., under the fg) of pix2:
1702 * |1 & 2|/|1|, where |...| means the number of fg pixels.
1703 * Note that this is different from the situation where
1704 * pix1 and pix2 are reversed.
1705 * (2) Both pix1 and pix2 are registered to the UL corners. A warning
1706 * is issued if pix1 and pix2 have different sizes.
1707 * (3) This can also be used to find the fraction of fg pixels in pix1
1708 * that are NOT under the fg of pix2: 1.0 - |1 & 2|/|1|
1709 * (4) If pix1 or pix2 are empty, this returns %fract = 0.0.
1710 * (5) For example, pix2 could be a frame around the outside of the
1711 * image, made from pixMakeFrameMask().
1712 * </pre>
1713 */
1714 l_ok
1715 pixFractionFgInMask(PIX *pix1,
1716 PIX *pix2,
1717 l_float32 *pfract)
1718 {
1719 l_int32 w1, h1, w2, h2, empty, count1, count3;
1720 PIX *pix3;
1721
1722 if (!pfract)
1723 return ERROR_INT("&fract not defined", __func__, 1);
1724 *pfract = 0.0;
1725 if (!pix1 || pixGetDepth(pix1) != 1)
1726 return ERROR_INT("pix1 not defined or not 1 bpp", __func__, 1);
1727 if (!pix2 || pixGetDepth(pix2) != 1)
1728 return ERROR_INT("pix2 not defined or not 1 bpp", __func__, 1);
1729
1730 pixGetDimensions(pix1, &w1, &h1, NULL);
1731 pixGetDimensions(pix2, &w2, &h2, NULL);
1732 if (w1 != w2 || h1 != h2) {
1733 L_INFO("sizes unequal: (w1,w2) = (%d,%d), (h1,h2) = (%d,%d)\n",
1734 __func__, w1, w2, h1, h2);
1735 }
1736 pixZero(pix1, &empty);
1737 if (empty) return 0;
1738 pixZero(pix2, &empty);
1739 if (empty) return 0;
1740
1741 pix3 = pixCopy(NULL, pix1);
1742 pixAnd(pix3, pix3, pix2);
1743 pixCountPixels(pix1, &count1, NULL); /* |1| */
1744 pixCountPixels(pix3, &count3, NULL); /* |1 & 2| */
1745 *pfract = (l_float32)count3 / (l_float32)count1;
1746 pixDestroy(&pix3);
1747 return 0;
1748 }
1749
1750
1751 /*---------------------------------------------------------------------*
1752 * Clip to Foreground *
1753 *---------------------------------------------------------------------*/
1754 /*!
1755 * \brief pixClipToForeground()
1756 *
1757 * \param[in] pixs 1 bpp
1758 * \param[out] ppixd [optional] clipped pix returned
1759 * \param[out] pbox [optional] bounding box
1760 * \return 0 if OK; 1 on error or if there are no fg pixels
1761 *
1762 * <pre>
1763 * Notes:
1764 * (1) At least one of {&pixd, &box} must be specified.
1765 * (2) If there are no fg pixels, the returned ptrs are null.
1766 * </pre>
1767 */
1768 l_ok
1769 pixClipToForeground(PIX *pixs,
1770 PIX **ppixd,
1771 BOX **pbox)
1772 {
1773 l_int32 w, h, wpl, nfullwords, extra, i, j;
1774 l_int32 minx, miny, maxx, maxy;
1775 l_uint32 result, mask;
1776 l_uint32 *data, *line;
1777 BOX *box;
1778
1779 if (ppixd) *ppixd = NULL;
1780 if (pbox) *pbox = NULL;
1781 if (!ppixd && !pbox)
1782 return ERROR_INT("no output requested", __func__, 1);
1783 if (!pixs || (pixGetDepth(pixs) != 1))
1784 return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1);
1785
1786 pixGetDimensions(pixs, &w, &h, NULL);
1787 nfullwords = w / 32;
1788 extra = w & 31;
1789 mask = ~rmask32[32 - extra];
1790 wpl = pixGetWpl(pixs);
1791 data = pixGetData(pixs);
1792
1793 result = 0;
1794 for (i = 0, miny = 0; i < h; i++, miny++) {
1795 line = data + i * wpl;
1796 for (j = 0; j < nfullwords; j++)
1797 result |= line[j];
1798 if (extra)
1799 result |= (line[j] & mask);
1800 if (result)
1801 break;
1802 }
1803 if (miny == h) /* no ON pixels */
1804 return 1;
1805
1806 result = 0;
1807 for (i = h - 1, maxy = h - 1; i >= 0; i--, maxy--) {
1808 line = data + i * wpl;
1809 for (j = 0; j < nfullwords; j++)
1810 result |= line[j];
1811 if (extra)
1812 result |= (line[j] & mask);
1813 if (result)
1814 break;
1815 }
1816
1817 minx = 0;
1818 for (j = 0, minx = 0; j < w; j++, minx++) {
1819 for (i = 0; i < h; i++) {
1820 line = data + i * wpl;
1821 if (GET_DATA_BIT(line, j))
1822 goto minx_found;
1823 }
1824 }
1825
1826 minx_found:
1827 for (j = w - 1, maxx = w - 1; j >= 0; j--, maxx--) {
1828 for (i = 0; i < h; i++) {
1829 line = data + i * wpl;
1830 if (GET_DATA_BIT(line, j))
1831 goto maxx_found;
1832 }
1833 }
1834
1835 maxx_found:
1836 box = boxCreate(minx, miny, maxx - minx + 1, maxy - miny + 1);
1837
1838 if (ppixd)
1839 *ppixd = pixClipRectangle(pixs, box, NULL);
1840 if (pbox)
1841 *pbox = box;
1842 else
1843 boxDestroy(&box);
1844
1845 return 0;
1846 }
1847
1848
1849 /*!
1850 * \brief pixTestClipToForeground()
1851 *
1852 * \param[in] pixs 1 bpp
1853 * \param[out] pcanclip 1 if fg does not extend to all four edges
1854 * \return 0 if OK; 1 on error
1855 *
1856 * <pre>
1857 * Notes:
1858 * (1) This is a lightweight test to determine if a 1 bpp image
1859 * can be further cropped without loss of fg pixels.
1860 * If it cannot, canclip is set to 0.
1861 * (2) It does not test for the existence of any fg pixels.
1862 * If there are no fg pixels, it will return %canclip = 1.
1863 * Check the output of the subsequent call to pixClipToForeground().
1864 * </pre>
1865 */
1866 l_ok
1867 pixTestClipToForeground(PIX *pixs,
1868 l_int32 *pcanclip)
1869 {
1870 l_int32 i, j, w, h, wpl, found;
1871 l_uint32 *data, *line;
1872
1873 if (!pcanclip)
1874 return ERROR_INT("&canclip not defined", __func__, 1);
1875 *pcanclip = 0;
1876 if (!pixs || (pixGetDepth(pixs) != 1))
1877 return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1);
1878
1879 /* Check top and bottom raster lines */
1880 pixGetDimensions(pixs, &w, &h, NULL);
1881 data = pixGetData(pixs);
1882 wpl = pixGetWpl(pixs);
1883 found = FALSE;
1884 for (j = 0; found == FALSE && j < w; j++)
1885 found = GET_DATA_BIT(data, j);
1886 if (!found) {
1887 *pcanclip = 1;
1888 return 0;
1889 }
1890
1891 line = data + (h - 1) * wpl;
1892 found = FALSE;
1893 for (j = 0; found == FALSE && j < w; j++)
1894 found = GET_DATA_BIT(data, j);
1895 if (!found) {
1896 *pcanclip = 1;
1897 return 0;
1898 }
1899
1900 /* Check left and right edges */
1901 found = FALSE;
1902 for (i = 0, line = data; found == FALSE && i < h; line += wpl, i++)
1903 found = GET_DATA_BIT(line, 0);
1904 if (!found) {
1905 *pcanclip = 1;
1906 return 0;
1907 }
1908
1909 found = FALSE;
1910 for (i = 0, line = data; found == FALSE && i < h; line += wpl, i++)
1911 found = GET_DATA_BIT(line, w - 1);
1912 if (!found)
1913 *pcanclip = 1;
1914
1915 return 0; /* fg pixels found on all edges */
1916 }
1917
1918
1919 /*!
1920 * \brief pixClipBoxToForeground()
1921 *
1922 * \param[in] pixs 1 bpp
1923 * \param[in] boxs [optional] use full image if null
1924 * \param[out] ppixd [optional] clipped pix returned
1925 * \param[out] pboxd [optional] bounding box
1926 * \return 0 if OK; 1 on error or if there are no fg pixels
1927 *
1928 * <pre>
1929 * Notes:
1930 * (1) At least one of {&pixd, &boxd} must be specified.
1931 * (2) If there are no fg pixels, the returned ptrs are null.
1932 * (3) Do not use &pixs for the 3rd arg or &boxs for the 4th arg;
1933 * this will leak memory.
1934 * </pre>
1935 */
1936 l_ok
1937 pixClipBoxToForeground(PIX *pixs,
1938 BOX *boxs,
1939 PIX **ppixd,
1940 BOX **pboxd)
1941 {
1942 l_int32 w, h, bx, by, bw, bh, cbw, cbh, left, right, top, bottom;
1943 BOX *boxt, *boxd;
1944
1945 if (ppixd) *ppixd = NULL;
1946 if (pboxd) *pboxd = NULL;
1947 if (!ppixd && !pboxd)
1948 return ERROR_INT("no output requested", __func__, 1);
1949 if (!pixs || (pixGetDepth(pixs) != 1))
1950 return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1);
1951
1952 if (!boxs)
1953 return pixClipToForeground(pixs, ppixd, pboxd);
1954
1955 pixGetDimensions(pixs, &w, &h, NULL);
1956 boxGetGeometry(boxs, &bx, &by, &bw, &bh);
1957 cbw = L_MIN(bw, w - bx);
1958 cbh = L_MIN(bh, h - by);
1959 if (cbw < 0 || cbh < 0)
1960 return ERROR_INT("box not within image", __func__, 1);
1961 boxt = boxCreate(bx, by, cbw, cbh);
1962
1963 if (pixScanForForeground(pixs, boxt, L_FROM_LEFT, &left)) {
1964 boxDestroy(&boxt);
1965 return 1;
1966 }
1967 pixScanForForeground(pixs, boxt, L_FROM_RIGHT, &right);
1968 pixScanForForeground(pixs, boxt, L_FROM_TOP, &top);
1969 pixScanForForeground(pixs, boxt, L_FROM_BOT, &bottom);
1970
1971 boxd = boxCreate(left, top, right - left + 1, bottom - top + 1);
1972 if (ppixd)
1973 *ppixd = pixClipRectangle(pixs, boxd, NULL);
1974 if (pboxd)
1975 *pboxd = boxd;
1976 else
1977 boxDestroy(&boxd);
1978
1979 boxDestroy(&boxt);
1980 return 0;
1981 }
1982
1983
1984 /*!
1985 * \brief pixScanForForeground()
1986 *
1987 * \param[in] pixs 1 bpp
1988 * \param[in] box [optional] within which the search is conducted
1989 * \param[in] scanflag direction of scan; e.g., L_FROM_LEFT
1990 * \param[out] ploc location in scan direction of first black pixel
1991 * \return 0 if OK; 1 on error or if no fg pixels are found
1992 *
1993 * <pre>
1994 * Notes:
1995 * (1) If there are no fg pixels, the position is set to 0.
1996 * Caller must check the return value!
1997 * (2) Use %box == NULL to scan from edge of pixs
1998 * </pre>
1999 */
2000 l_ok
2001 pixScanForForeground(PIX *pixs,
2002 BOX *box,
2003 l_int32 scanflag,
2004 l_int32 *ploc)
2005 {
2006 l_int32 bx, by, bw, bh, x, xstart, xend, y, ystart, yend, wpl;
2007 l_uint32 *data, *line;
2008 BOX *boxt;
2009
2010 if (!ploc)
2011 return ERROR_INT("&loc not defined", __func__, 1);
2012 *ploc = 0;
2013 if (!pixs || (pixGetDepth(pixs) != 1))
2014 return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1);
2015
2016 /* Clip box to pixs if it exists */
2017 pixGetDimensions(pixs, &bw, &bh, NULL);
2018 if (box) {
2019 if ((boxt = boxClipToRectangle(box, bw, bh)) == NULL)
2020 return ERROR_INT("invalid box", __func__, 1);
2021 boxGetGeometry(boxt, &bx, &by, &bw, &bh);
2022 boxDestroy(&boxt);
2023 } else {
2024 bx = by = 0;
2025 }
2026 xstart = bx;
2027 ystart = by;
2028 xend = bx + bw - 1;
2029 yend = by + bh - 1;
2030
2031 data = pixGetData(pixs);
2032 wpl = pixGetWpl(pixs);
2033 if (scanflag == L_FROM_LEFT) {
2034 for (x = xstart; x <= xend; x++) {
2035 for (y = ystart; y <= yend; y++) {
2036 line = data + y * wpl;
2037 if (GET_DATA_BIT(line, x)) {
2038 *ploc = x;
2039 return 0;
2040 }
2041 }
2042 }
2043 } else if (scanflag == L_FROM_RIGHT) {
2044 for (x = xend; x >= xstart; x--) {
2045 for (y = ystart; y <= yend; y++) {
2046 line = data + y * wpl;
2047 if (GET_DATA_BIT(line, x)) {
2048 *ploc = x;
2049 return 0;
2050 }
2051 }
2052 }
2053 } else if (scanflag == L_FROM_TOP) {
2054 for (y = ystart; y <= yend; y++) {
2055 line = data + y * wpl;
2056 for (x = xstart; x <= xend; x++) {
2057 if (GET_DATA_BIT(line, x)) {
2058 *ploc = y;
2059 return 0;
2060 }
2061 }
2062 }
2063 } else if (scanflag == L_FROM_BOT) {
2064 for (y = yend; y >= ystart; y--) {
2065 line = data + y * wpl;
2066 for (x = xstart; x <= xend; x++) {
2067 if (GET_DATA_BIT(line, x)) {
2068 *ploc = y;
2069 return 0;
2070 }
2071 }
2072 }
2073 } else {
2074 return ERROR_INT("invalid scanflag", __func__, 1);
2075 }
2076
2077 return 1; /* no fg found */
2078 }
2079
2080
2081 /*!
2082 * \brief pixClipBoxToEdges()
2083 *
2084 * \param[in] pixs 1 bpp
2085 * \param[in] boxs [optional] ; use full image if null
2086 * \param[in] lowthresh threshold to choose clipping location
2087 * \param[in] highthresh threshold required to find an edge
2088 * \param[in] maxwidth max allowed width between low and high thresh locs
2089 * \param[in] factor sampling factor along pixel counting direction
2090 * \param[out] ppixd [optional] clipped pix returned
2091 * \param[out] pboxd [optional] bounding box
2092 * \return 0 if OK; 1 on error or if a fg edge is not found from
2093 * all four sides.
2094 *
2095 * <pre>
2096 * Notes:
2097 * (1) At least one of {&pixd, &boxd} must be specified.
2098 * (2) If there are no fg pixels, the returned ptrs are null.
2099 * (3) This function attempts to locate rectangular "image" regions
2100 * of high-density fg pixels, that have well-defined edges
2101 * on the four sides.
2102 * (4) Edges are searched for on each side, iterating in order
2103 * from left, right, top and bottom. As each new edge is
2104 * found, the search box is resized to use that location.
2105 * Once an edge is found, it is held. If no more edges
2106 * are found in one iteration, the search fails.
2107 * (5) See pixScanForEdge() for usage of the thresholds and %maxwidth.
2108 * (6) The thresholds must be at least 1, and the low threshold
2109 * cannot be larger than the high threshold.
2110 * (7) If the low and high thresholds are both 1, this is equivalent
2111 * to pixClipBoxToForeground().
2112 * </pre>
2113 */
2114 l_ok
2115 pixClipBoxToEdges(PIX *pixs,
2116 BOX *boxs,
2117 l_int32 lowthresh,
2118 l_int32 highthresh,
2119 l_int32 maxwidth,
2120 l_int32 factor,
2121 PIX **ppixd,
2122 BOX **pboxd)
2123 {
2124 l_int32 w, h, bx, by, bw, bh, cbw, cbh, left, right, top, bottom;
2125 l_int32 lfound, rfound, tfound, bfound, change;
2126 BOX *boxt, *boxd;
2127
2128 if (ppixd) *ppixd = NULL;
2129 if (pboxd) *pboxd = NULL;
2130 if (!ppixd && !pboxd)
2131 return ERROR_INT("no output requested", __func__, 1);
2132 if (!pixs || (pixGetDepth(pixs) != 1))
2133 return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1);
2134 if (lowthresh < 1 || highthresh < 1 ||
2135 lowthresh > highthresh || maxwidth < 1)
2136 return ERROR_INT("invalid thresholds", __func__, 1);
2137 factor = L_MIN(1, factor);
2138
2139 if (lowthresh == 1 && highthresh == 1)
2140 return pixClipBoxToForeground(pixs, boxs, ppixd, pboxd);
2141
2142 pixGetDimensions(pixs, &w, &h, NULL);
2143 if (boxs) {
2144 boxGetGeometry(boxs, &bx, &by, &bw, &bh);
2145 cbw = L_MIN(bw, w - bx);
2146 cbh = L_MIN(bh, h - by);
2147 if (cbw < 0 || cbh < 0)
2148 return ERROR_INT("box not within image", __func__, 1);
2149 boxt = boxCreate(bx, by, cbw, cbh);
2150 } else {
2151 boxt = boxCreate(0, 0, w, h);
2152 }
2153
2154 lfound = rfound = tfound = bfound = 0;
2155 while (!lfound || !rfound || !tfound || !bfound) {
2156 change = 0;
2157 if (!lfound) {
2158 if (!pixScanForEdge(pixs, boxt, lowthresh, highthresh, maxwidth,
2159 factor, L_FROM_LEFT, &left)) {
2160 lfound = 1;
2161 change = 1;
2162 boxRelocateOneSide(boxt, boxt, left, L_FROM_LEFT);
2163 }
2164 }
2165 if (!rfound) {
2166 if (!pixScanForEdge(pixs, boxt, lowthresh, highthresh, maxwidth,
2167 factor, L_FROM_RIGHT, &right)) {
2168 rfound = 1;
2169 change = 1;
2170 boxRelocateOneSide(boxt, boxt, right, L_FROM_RIGHT);
2171 }
2172 }
2173 if (!tfound) {
2174 if (!pixScanForEdge(pixs, boxt, lowthresh, highthresh, maxwidth,
2175 factor, L_FROM_TOP, &top)) {
2176 tfound = 1;
2177 change = 1;
2178 boxRelocateOneSide(boxt, boxt, top, L_FROM_TOP);
2179 }
2180 }
2181 if (!bfound) {
2182 if (!pixScanForEdge(pixs, boxt, lowthresh, highthresh, maxwidth,
2183 factor, L_FROM_BOT, &bottom)) {
2184 bfound = 1;
2185 change = 1;
2186 boxRelocateOneSide(boxt, boxt, bottom, L_FROM_BOT);
2187 }
2188 }
2189
2190 #if DEBUG_EDGES
2191 lept_stderr("iter: %d %d %d %d\n", lfound, rfound, tfound, bfound);
2192 #endif /* DEBUG_EDGES */
2193
2194 if (change == 0) break;
2195 }
2196 boxDestroy(&boxt);
2197
2198 if (change == 0)
2199 return ERROR_INT("not all edges found", __func__, 1);
2200
2201 boxd = boxCreate(left, top, right - left + 1, bottom - top + 1);
2202 if (ppixd)
2203 *ppixd = pixClipRectangle(pixs, boxd, NULL);
2204 if (pboxd)
2205 *pboxd = boxd;
2206 else
2207 boxDestroy(&boxd);
2208
2209 return 0;
2210 }
2211
2212
2213 /*!
2214 * \brief pixScanForEdge()
2215 *
2216 * \param[in] pixs 1 bpp
2217 * \param[in] box [optional] within which the search is conducted
2218 * \param[in] lowthresh threshold to choose clipping location
2219 * \param[in] highthresh threshold required to find an edge
2220 * \param[in] maxwidth max allowed width between low and high thresh locs
2221 * \param[in] factor sampling factor along pixel counting direction
2222 * \param[in] scanflag direction of scan; e.g., L_FROM_LEFT
2223 * \param[out] ploc location in scan direction of first black pixel
2224 * \return 0 if OK; 1 on error or if the edge is not found
2225 *
2226 * <pre>
2227 * Notes:
2228 * (1) If there are no fg pixels, the position is set to 0.
2229 * Caller must check the return value!
2230 * (2) Use %box == NULL to scan from edge of pixs
2231 * (3) As the scan progresses, the location where the sum of
2232 * pixels equals or excees %lowthresh is noted (loc). The
2233 * scan is stopped when the sum of pixels equals or exceeds
2234 * %highthresh. If the scan distance between loc and that
2235 * point does not exceed %maxwidth, an edge is found and
2236 * its position is taken to be loc. %maxwidth implicitly
2237 * sets a minimum on the required gradient of the edge.
2238 * (4) The thresholds must be at least 1, and the low threshold
2239 * cannot be larger than the high threshold.
2240 * </pre>
2241 */
2242 l_ok
2243 pixScanForEdge(PIX *pixs,
2244 BOX *box,
2245 l_int32 lowthresh,
2246 l_int32 highthresh,
2247 l_int32 maxwidth,
2248 l_int32 factor,
2249 l_int32 scanflag,
2250 l_int32 *ploc)
2251 {
2252 l_int32 bx, by, bw, bh, foundmin, loc, sum, wpl;
2253 l_int32 x, xstart, xend, y, ystart, yend;
2254 l_uint32 *data, *line;
2255 BOX *boxt;
2256
2257 if (!ploc)
2258 return ERROR_INT("&ploc not defined", __func__, 1);
2259 *ploc = 0;
2260 if (!pixs || (pixGetDepth(pixs) != 1))
2261 return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1);
2262 if (lowthresh < 1 || highthresh < 1 ||
2263 lowthresh > highthresh || maxwidth < 1)
2264 return ERROR_INT("invalid thresholds", __func__, 1);
2265 factor = L_MIN(1, factor);
2266
2267 /* Clip box to pixs if it exists */
2268 pixGetDimensions(pixs, &bw, &bh, NULL);
2269 if (box) {
2270 if ((boxt = boxClipToRectangle(box, bw, bh)) == NULL)
2271 return ERROR_INT("invalid box", __func__, 1);
2272 boxGetGeometry(boxt, &bx, &by, &bw, &bh);
2273 boxDestroy(&boxt);
2274 } else {
2275 bx = by = 0;
2276 }
2277 xstart = bx;
2278 ystart = by;
2279 xend = bx + bw - 1;
2280 yend = by + bh - 1;
2281
2282 data = pixGetData(pixs);
2283 wpl = pixGetWpl(pixs);
2284 foundmin = 0;
2285 if (scanflag == L_FROM_LEFT) {
2286 for (x = xstart; x <= xend; x++) {
2287 sum = 0;
2288 for (y = ystart; y <= yend; y += factor) {
2289 line = data + y * wpl;
2290 if (GET_DATA_BIT(line, x))
2291 sum++;
2292 }
2293 if (!foundmin && sum < lowthresh)
2294 continue;
2295 if (!foundmin) { /* save the loc of the beginning of the edge */
2296 foundmin = 1;
2297 loc = x;
2298 }
2299 if (sum >= highthresh) {
2300 #if DEBUG_EDGES
2301 lept_stderr("Left: x = %d, loc = %d\n", x, loc);
2302 #endif /* DEBUG_EDGES */
2303 if (x - loc < maxwidth) {
2304 *ploc = loc;
2305 return 0;
2306 } else {
2307 return 1;
2308 }
2309 }
2310 }
2311 } else if (scanflag == L_FROM_RIGHT) {
2312 for (x = xend; x >= xstart; x--) {
2313 sum = 0;
2314 for (y = ystart; y <= yend; y += factor) {
2315 line = data + y * wpl;
2316 if (GET_DATA_BIT(line, x))
2317 sum++;
2318 }
2319 if (!foundmin && sum < lowthresh)
2320 continue;
2321 if (!foundmin) {
2322 foundmin = 1;
2323 loc = x;
2324 }
2325 if (sum >= highthresh) {
2326 #if DEBUG_EDGES
2327 lept_stderr("Right: x = %d, loc = %d\n", x, loc);
2328 #endif /* DEBUG_EDGES */
2329 if (loc - x < maxwidth) {
2330 *ploc = loc;
2331 return 0;
2332 } else {
2333 return 1;
2334 }
2335 }
2336 }
2337 } else if (scanflag == L_FROM_TOP) {
2338 for (y = ystart; y <= yend; y++) {
2339 sum = 0;
2340 line = data + y * wpl;
2341 for (x = xstart; x <= xend; x += factor) {
2342 if (GET_DATA_BIT(line, x))
2343 sum++;
2344 }
2345 if (!foundmin && sum < lowthresh)
2346 continue;
2347 if (!foundmin) {
2348 foundmin = 1;
2349 loc = y;
2350 }
2351 if (sum >= highthresh) {
2352 #if DEBUG_EDGES
2353 lept_stderr("Top: y = %d, loc = %d\n", y, loc);
2354 #endif /* DEBUG_EDGES */
2355 if (y - loc < maxwidth) {
2356 *ploc = loc;
2357 return 0;
2358 } else {
2359 return 1;
2360 }
2361 }
2362 }
2363 } else if (scanflag == L_FROM_BOT) {
2364 for (y = yend; y >= ystart; y--) {
2365 sum = 0;
2366 line = data + y * wpl;
2367 for (x = xstart; x <= xend; x += factor) {
2368 if (GET_DATA_BIT(line, x))
2369 sum++;
2370 }
2371 if (!foundmin && sum < lowthresh)
2372 continue;
2373 if (!foundmin) {
2374 foundmin = 1;
2375 loc = y;
2376 }
2377 if (sum >= highthresh) {
2378 #if DEBUG_EDGES
2379 lept_stderr("Bottom: y = %d, loc = %d\n", y, loc);
2380 #endif /* DEBUG_EDGES */
2381 if (loc - y < maxwidth) {
2382 *ploc = loc;
2383 return 0;
2384 } else {
2385 return 1;
2386 }
2387 }
2388 }
2389 } else {
2390 return ERROR_INT("invalid scanflag", __func__, 1);
2391 }
2392
2393 return 1; /* edge not found */
2394 }
2395
2396
2397 /*---------------------------------------------------------------------*
2398 * Extract pixel averages and reversals along lines *
2399 *---------------------------------------------------------------------*/
2400 /*!
2401 * \brief pixExtractOnLine()
2402 *
2403 * \param[in] pixs 1 bpp or 8 bpp; no colormap
2404 * \param[in] x1, y1 one end point for line
2405 * \param[in] x2, y2 another end pt for line
2406 * \param[in] factor sampling; >= 1
2407 * \return na of pixel values along line, or NULL on error.
2408 *
2409 * <pre>
2410 * Notes:
2411 * (1) Input end points are clipped to the pix.
2412 * (2) If the line is either horizontal, or closer to horizontal
2413 * than to vertical, the points will be extracted from left
2414 * to right in the pix. Likewise, if the line is vertical,
2415 * or closer to vertical than to horizontal, the points will
2416 * be extracted from top to bottom.
2417 * (3) Can be used with numaCountReverals(), for example, to
2418 * characterize the intensity smoothness along a line.
2419 * </pre>
2420 */
2421 NUMA *
2422 pixExtractOnLine(PIX *pixs,
2423 l_int32 x1,
2424 l_int32 y1,
2425 l_int32 x2,
2426 l_int32 y2,
2427 l_int32 factor)
2428 {
2429 l_int32 i, w, h, d, xmin, ymin, xmax, ymax, npts, direction;
2430 l_uint32 val;
2431 l_float32 x, y;
2432 l_float64 slope;
2433 NUMA *na;
2434 PTA *pta;
2435
2436 if (!pixs)
2437 return (NUMA *)ERROR_PTR("pixs not defined", __func__, NULL);
2438 pixGetDimensions(pixs, &w, &h, &d);
2439 if (d != 1 && d != 8)
2440 return (NUMA *)ERROR_PTR("d not 1 or 8 bpp", __func__, NULL);
2441 if (pixGetColormap(pixs))
2442 return (NUMA *)ERROR_PTR("pixs has a colormap", __func__, NULL);
2443 if (factor < 1) {
2444 L_WARNING("factor must be >= 1; setting to 1\n", __func__);
2445 factor = 1;
2446 }
2447
2448 /* Clip line to the image */
2449 x1 = L_MAX(0, L_MIN(x1, w - 1));
2450 x2 = L_MAX(0, L_MIN(x2, w - 1));
2451 y1 = L_MAX(0, L_MIN(y1, h - 1));
2452 y2 = L_MAX(0, L_MIN(y2, h - 1));
2453
2454 if (x1 == x2 && y1 == y2) {
2455 pixGetPixel(pixs, x1, y1, &val);
2456 na = numaCreate(1);
2457 numaAddNumber(na, val);
2458 return na;
2459 }
2460
2461 if (y1 == y2)
2462 direction = L_HORIZONTAL_LINE;
2463 else if (x1 == x2)
2464 direction = L_VERTICAL_LINE;
2465 else
2466 direction = L_OBLIQUE_LINE;
2467
2468 na = numaCreate(0);
2469 if (direction == L_HORIZONTAL_LINE) { /* plot against x */
2470 xmin = L_MIN(x1, x2);
2471 xmax = L_MAX(x1, x2);
2472 numaSetParameters(na, xmin, factor);
2473 for (i = xmin; i <= xmax; i += factor) {
2474 pixGetPixel(pixs, i, y1, &val);
2475 numaAddNumber(na, val);
2476 }
2477 } else if (direction == L_VERTICAL_LINE) { /* plot against y */
2478 ymin = L_MIN(y1, y2);
2479 ymax = L_MAX(y1, y2);
2480 numaSetParameters(na, ymin, factor);
2481 for (i = ymin; i <= ymax; i += factor) {
2482 pixGetPixel(pixs, x1, i, &val);
2483 numaAddNumber(na, val);
2484 }
2485 } else { /* direction == L_OBLIQUE_LINE */
2486 slope = (l_float64)((y2 - y1) / (x2 - x1));
2487 if (L_ABS(slope) < 1.0) { /* quasi-horizontal */
2488 xmin = L_MIN(x1, x2);
2489 xmax = L_MAX(x1, x2);
2490 ymin = (xmin == x1) ? y1 : y2; /* pt that goes with xmin */
2491 ymax = (ymin == y1) ? y2 : y1; /* pt that goes with xmax */
2492 pta = generatePtaLine(xmin, ymin, xmax, ymax);
2493 numaSetParameters(na, xmin, (l_float32)factor);
2494 } else { /* quasi-vertical */
2495 ymin = L_MIN(y1, y2);
2496 ymax = L_MAX(y1, y2);
2497 xmin = (ymin == y1) ? x1 : x2; /* pt that goes with ymin */
2498 xmax = (xmin == x1) ? x2 : x1; /* pt that goes with ymax */
2499 pta = generatePtaLine(xmin, ymin, xmax, ymax);
2500 numaSetParameters(na, ymin, (l_float32)factor);
2501 }
2502 npts = ptaGetCount(pta);
2503 for (i = 0; i < npts; i += factor) {
2504 ptaGetPt(pta, i, &x, &y);
2505 pixGetPixel(pixs, (l_int32)x, (l_int32)y, &val);
2506 numaAddNumber(na, val);
2507 }
2508
2509 #if 0 /* debugging */
2510 pixPlotAlongPta(pixs, pta, GPLOT_PNG, NULL);
2511 #endif
2512
2513 ptaDestroy(&pta);
2514 }
2515
2516 return na;
2517 }
2518
2519
2520 /*!
2521 * \brief pixAverageOnLine()
2522 *
2523 * \param[in] pixs 1 bpp or 8 bpp; no colormap
2524 * \param[in] x1, y1 starting pt for line
2525 * \param[in] x2, y2 end pt for line
2526 * \param[in] factor sampling; >= 1
2527 * \return average of pixel values along line, or NULL on error.
2528 *
2529 * <pre>
2530 * Notes:
2531 * (1) The line must be either horizontal or vertical, so either
2532 * y1 == y2 (horizontal) or x1 == x2 (vertical).
2533 * (2) If horizontal, x1 must be <= x2.
2534 * If vertical, y1 must be <= y2.
2535 * characterize the intensity smoothness along a line.
2536 * (3) Input end points are clipped to the pix.
2537 * </pre>
2538 */
2539 l_float32
2540 pixAverageOnLine(PIX *pixs,
2541 l_int32 x1,
2542 l_int32 y1,
2543 l_int32 x2,
2544 l_int32 y2,
2545 l_int32 factor)
2546 {
2547 l_int32 i, j, w, h, d, direction, count, wpl;
2548 l_uint32 *data, *line;
2549 l_float32 sum;
2550
2551 if (!pixs)
2552 return ERROR_INT("pixs not defined", __func__, 1);
2553 pixGetDimensions(pixs, &w, &h, &d);
2554 if (d != 1 && d != 8)
2555 return ERROR_INT("d not 1 or 8 bpp", __func__, 1);
2556 if (pixGetColormap(pixs))
2557 return ERROR_INT("pixs has a colormap", __func__, 1);
2558 if (x1 > x2 || y1 > y2)
2559 return ERROR_INT("x1 > x2 or y1 > y2", __func__, 1);
2560
2561 if (y1 == y2) {
2562 x1 = L_MAX(0, x1);
2563 x2 = L_MIN(w - 1, x2);
2564 y1 = L_MAX(0, L_MIN(y1, h - 1));
2565 direction = L_HORIZONTAL_LINE;
2566 } else if (x1 == x2) {
2567 y1 = L_MAX(0, y1);
2568 y2 = L_MIN(h - 1, y2);
2569 x1 = L_MAX(0, L_MIN(x1, w - 1));
2570 direction = L_VERTICAL_LINE;
2571 } else {
2572 return ERROR_INT("line neither horiz nor vert", __func__, 1);
2573 }
2574
2575 if (factor < 1) {
2576 L_WARNING("factor must be >= 1; setting to 1\n", __func__);
2577 factor = 1;
2578 }
2579
2580 data = pixGetData(pixs);
2581 wpl = pixGetWpl(pixs);
2582 sum = 0;
2583 count = 0;
2584 if (direction == L_HORIZONTAL_LINE) {
2585 line = data + y1 * wpl;
2586 for (j = x1, count = 0; j <= x2; count++, j += factor) {
2587 if (d == 1)
2588 sum += GET_DATA_BIT(line, j);
2589 else /* d == 8 */
2590 sum += GET_DATA_BYTE(line, j);
2591 }
2592 } else if (direction == L_VERTICAL_LINE) {
2593 for (i = y1, count = 0; i <= y2; count++, i += factor) {
2594 line = data + i * wpl;
2595 if (d == 1)
2596 sum += GET_DATA_BIT(line, x1);
2597 else /* d == 8 */
2598 sum += GET_DATA_BYTE(line, x1);
2599 }
2600 }
2601
2602 return sum / (l_float32)count;
2603 }
2604
2605
2606 /*!
2607 * \brief pixAverageIntensityProfile()
2608 *
2609 * \param[in] pixs any depth; colormap OK
2610 * \param[in] fract fraction of image width or height to be used
2611 * \param[in] dir averaging direction: L_HORIZONTAL_LINE or
2612 * L_VERTICAL_LINE
2613 * \param[in] first, last span of rows or columns to measure
2614 * \param[in] factor1 sampling along fast scan direction; >= 1
2615 * \param[in] factor2 sampling along slow scan direction; >= 1
2616 * \return na of reversal profile, or NULL on error.
2617 *
2618 * <pre>
2619 * Notes:
2620 * (1) If d != 1 bpp, colormaps are removed and the result
2621 * is converted to 8 bpp.
2622 * (2) If %dir == L_HORIZONTAL_LINE, the intensity is averaged
2623 * along each horizontal raster line (sampled by %factor1),
2624 * and the profile is the array of these averages in the
2625 * vertical direction between %first and %last raster lines,
2626 * and sampled by %factor2.
2627 * (3) If %dir == L_VERTICAL_LINE, the intensity is averaged
2628 * along each vertical line (sampled by %factor1),
2629 * and the profile is the array of these averages in the
2630 * horizontal direction between %first and %last columns,
2631 * and sampled by %factor2.
2632 * (4) The averages are measured over the central %fract of the image.
2633 * Use %fract == 1.0 to average across the entire width or height.
2634 * </pre>
2635 */
2636 NUMA *
2637 pixAverageIntensityProfile(PIX *pixs,
2638 l_float32 fract,
2639 l_int32 dir,
2640 l_int32 first,
2641 l_int32 last,
2642 l_int32 factor1,
2643 l_int32 factor2)
2644 {
2645 l_int32 i, j, w, h, d, start, end;
2646 l_float32 ave;
2647 NUMA *nad;
2648 PIX *pixr, *pixg;
2649
2650 if (!pixs)
2651 return (NUMA *)ERROR_PTR("pixs not defined", __func__, NULL);
2652 if (fract < 0.0 || fract > 1.0)
2653 return (NUMA *)ERROR_PTR("fract < 0.0 or > 1.0", __func__, NULL);
2654 if (dir != L_HORIZONTAL_LINE && dir != L_VERTICAL_LINE)
2655 return (NUMA *)ERROR_PTR("invalid direction", __func__, NULL);
2656 if (first < 0) first = 0;
2657 if (last < first)
2658 return (NUMA *)ERROR_PTR("last must be >= first", __func__, NULL);
2659 if (factor1 < 1) {
2660 L_WARNING("factor1 must be >= 1; setting to 1\n", __func__);
2661 factor1 = 1;
2662 }
2663 if (factor2 < 1) {
2664 L_WARNING("factor2 must be >= 1; setting to 1\n", __func__);
2665 factor2 = 1;
2666 }
2667
2668 /* Use 1 or 8 bpp, without colormap */
2669 if (pixGetColormap(pixs))
2670 pixr = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
2671 else
2672 pixr = pixClone(pixs);
2673 pixGetDimensions(pixr, &w, &h, &d);
2674 if (d == 1)
2675 pixg = pixClone(pixr);
2676 else
2677 pixg = pixConvertTo8(pixr, 0);
2678
2679 nad = numaCreate(0); /* output: samples in slow scan direction */
2680 numaSetParameters(nad, 0, factor2);
2681 if (dir == L_HORIZONTAL_LINE) {
2682 start = (l_int32)(0.5 * (1.0 - fract) * (l_float32)w);
2683 end = w - start;
2684 if (last > h - 1) {
2685 L_WARNING("last > h - 1; clipping\n", __func__);
2686 last = h - 1;
2687 }
2688 for (i = first; i <= last; i += factor2) {
2689 ave = pixAverageOnLine(pixg, start, i, end, i, factor1);
2690 numaAddNumber(nad, ave);
2691 }
2692 } else if (dir == L_VERTICAL_LINE) {
2693 start = (l_int32)(0.5 * (1.0 - fract) * (l_float32)h);
2694 end = h - start;
2695 if (last > w - 1) {
2696 L_WARNING("last > w - 1; clipping\n", __func__);
2697 last = w - 1;
2698 }
2699 for (j = first; j <= last; j += factor2) {
2700 ave = pixAverageOnLine(pixg, j, start, j, end, factor1);
2701 numaAddNumber(nad, ave);
2702 }
2703 }
2704
2705 pixDestroy(&pixr);
2706 pixDestroy(&pixg);
2707 return nad;
2708 }
2709
2710
2711 /*!
2712 * \brief pixReversalProfile()
2713 *
2714 * \param[in] pixs any depth; colormap OK
2715 * \param[in] fract fraction of image width or height to be used
2716 * \param[in] dir profile direction: L_HORIZONTAL_LINE or
2717 * L_VERTICAL_LINE
2718 * \param[in] first, last span of rows or columns to measure
2719 * \param[in] minreversal minimum change in intensity to trigger a reversal
2720 * \param[in] factor1 sampling along raster line (fast scan); >= 1
2721 * \param[in] factor2 sampling of raster lines (slow scan); >= 1
2722 * \return na of reversal profile, or NULL on error.
2723 *
2724 * <pre>
2725 * Notes:
2726 * (1) If d != 1 bpp, colormaps are removed and the result
2727 * is converted to 8 bpp.
2728 * (2) If %dir == L_HORIZONTAL_LINE, the the reversals are counted
2729 * along each horizontal raster line (sampled by %factor1),
2730 * and the profile is the array of these sums in the
2731 * vertical direction between %first and %last raster lines,
2732 * and sampled by %factor2.
2733 * (3) If %dir == L_VERTICAL_LINE, the the reversals are counted
2734 * along each vertical column (sampled by %factor1),
2735 * and the profile is the array of these sums in the
2736 * horizontal direction between %first and %last columns,
2737 * and sampled by %factor2.
2738 * (4) For each row or column, the reversals are summed over the
2739 * central %fract of the image. Use %fract == 1.0 to sum
2740 * across the entire width (of row) or height (of column).
2741 * (5) %minreversal is the relative change in intensity that is
2742 * required to resolve peaks and valleys. A typical number for
2743 * locating text in 8 bpp might be 50. For 1 bpp, minreversal
2744 * must be 1.
2745 * (6) The reversal profile is simply the number of reversals
2746 * in a row or column, vs the row or column index.
2747 * </pre>
2748 */
2749 NUMA *
2750 pixReversalProfile(PIX *pixs,
2751 l_float32 fract,
2752 l_int32 dir,
2753 l_int32 first,
2754 l_int32 last,
2755 l_int32 minreversal,
2756 l_int32 factor1,
2757 l_int32 factor2)
2758 {
2759 l_int32 i, j, w, h, d, start, end, nr;
2760 NUMA *naline, *nad;
2761 PIX *pixr, *pixg;
2762
2763 if (!pixs)
2764 return (NUMA *)ERROR_PTR("pixs not defined", __func__, NULL);
2765 if (fract < 0.0 || fract > 1.0)
2766 return (NUMA *)ERROR_PTR("fract < 0.0 or > 1.0", __func__, NULL);
2767 if (dir != L_HORIZONTAL_LINE && dir != L_VERTICAL_LINE)
2768 return (NUMA *)ERROR_PTR("invalid direction", __func__, NULL);
2769 if (first < 0) first = 0;
2770 if (last < first)
2771 return (NUMA *)ERROR_PTR("last must be >= first", __func__, NULL);
2772 if (factor1 < 1) {
2773 L_WARNING("factor1 must be >= 1; setting to 1\n", __func__);
2774 factor1 = 1;
2775 }
2776 if (factor2 < 1) {
2777 L_WARNING("factor2 must be >= 1; setting to 1\n", __func__);
2778 factor2 = 1;
2779 }
2780
2781 /* Use 1 or 8 bpp, without colormap */
2782 if (pixGetColormap(pixs))
2783 pixr = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
2784 else
2785 pixr = pixClone(pixs);
2786 pixGetDimensions(pixr, &w, &h, &d);
2787 if (d == 1) {
2788 pixg = pixClone(pixr);
2789 minreversal = 1; /* enforce this */
2790 } else {
2791 pixg = pixConvertTo8(pixr, 0);
2792 }
2793
2794 nad = numaCreate(0); /* output: samples in slow scan direction */
2795 numaSetParameters(nad, 0, factor2);
2796 if (dir == L_HORIZONTAL_LINE) {
2797 start = (l_int32)(0.5 * (1.0 - fract) * (l_float32)w);
2798 end = w - start;
2799 if (last > h - 1) {
2800 L_WARNING("last > h - 1; clipping\n", __func__);
2801 last = h - 1;
2802 }
2803 for (i = first; i <= last; i += factor2) {
2804 naline = pixExtractOnLine(pixg, start, i, end, i, factor1);
2805 numaCountReversals(naline, minreversal, &nr, NULL);
2806 numaAddNumber(nad, nr);
2807 numaDestroy(&naline);
2808 }
2809 } else if (dir == L_VERTICAL_LINE) {
2810 start = (l_int32)(0.5 * (1.0 - fract) * (l_float32)h);
2811 end = h - start;
2812 if (last > w - 1) {
2813 L_WARNING("last > w - 1; clipping\n", __func__);
2814 last = w - 1;
2815 }
2816 for (j = first; j <= last; j += factor2) {
2817 naline = pixExtractOnLine(pixg, j, start, j, end, factor1);
2818 numaCountReversals(naline, minreversal, &nr, NULL);
2819 numaAddNumber(nad, nr);
2820 numaDestroy(&naline);
2821 }
2822 }
2823
2824 pixDestroy(&pixr);
2825 pixDestroy(&pixg);
2826 return nad;
2827 }
2828
2829
2830 /*---------------------------------------------------------------------*
2831 * Extract windowed variance along a line *
2832 *---------------------------------------------------------------------*/
2833 /*!
2834 * \brief pixWindowedVarianceOnLine()
2835 *
2836 * \param[in] pixs 8 bpp; no colormap
2837 * \param[in] dir L_HORIZONTAL_LINE or L_VERTICAL_LINE
2838 * \param[in] loc location of the constant coordinate for the line
2839 * \param[in] c1, c2 end point coordinates for the line
2840 * \param[in] size window size; must be > 1
2841 * \param[out] pnad windowed square root of variance
2842 * \return 0 if OK; 1 on error
2843 *
2844 * <pre>
2845 * Notes:
2846 * (1) The returned variance array traverses the line starting
2847 * from the smallest coordinate, min(c1,c2).
2848 * (2) Line end points are clipped to pixs.
2849 * (3) The reference point for the variance calculation is the center of
2850 * the window. Therefore, the numa start parameter from
2851 * pixExtractOnLine() is incremented by %size/2,
2852 * to align the variance values with the pixel coordinate.
2853 * (4) The square root of the variance is the RMS deviation from the mean.
2854 * </pre>
2855 */
2856 l_ok
2857 pixWindowedVarianceOnLine(PIX *pixs,
2858 l_int32 dir,
2859 l_int32 loc,
2860 l_int32 c1,
2861 l_int32 c2,
2862 l_int32 size,
2863 NUMA **pnad)
2864 {
2865 l_int32 i, j, w, h, cmin, cmax, maxloc, n, x, y;
2866 l_uint32 val;
2867 l_float32 norm, rootvar;
2868 l_float32 *array;
2869 l_float64 sum1, sum2, ave, var;
2870 NUMA *na1, *nad;
2871 PTA *pta;
2872
2873 if (!pnad)
2874 return ERROR_INT("&nad not defined", __func__, 1);
2875 *pnad = NULL;
2876 if (!pixs || pixGetDepth(pixs) != 8)
2877 return ERROR_INT("pixs not defined or not 8bpp", __func__, 1);
2878 if (size < 2)
2879 return ERROR_INT("window size must be > 1", __func__, 1);
2880 if (dir != L_HORIZONTAL_LINE && dir != L_VERTICAL_LINE)
2881 return ERROR_INT("invalid direction", __func__, 1);
2882 pixGetDimensions(pixs, &w, &h, NULL);
2883 maxloc = (dir == L_HORIZONTAL_LINE) ? h - 1 : w - 1;
2884 if (loc < 0 || loc > maxloc)
2885 return ERROR_INT("invalid line position", __func__, 1);
2886
2887 /* Clip line to the image */
2888 cmin = L_MIN(c1, c2);
2889 cmax = L_MAX(c1, c2);
2890 maxloc = (dir == L_HORIZONTAL_LINE) ? w - 1 : h - 1;
2891 cmin = L_MAX(0, L_MIN(cmin, maxloc));
2892 cmax = L_MAX(0, L_MIN(cmax, maxloc));
2893 n = cmax - cmin + 1;
2894
2895 /* Generate pta along the line */
2896 pta = ptaCreate(n);
2897 if (dir == L_HORIZONTAL_LINE) {
2898 for (i = cmin; i <= cmax; i++)
2899 ptaAddPt(pta, i, loc);
2900 } else { /* vertical line */
2901 for (i = cmin; i <= cmax; i++)
2902 ptaAddPt(pta, loc, i);
2903 }
2904
2905 /* Get numa of pixel values on the line */
2906 na1 = numaCreate(n);
2907 numaSetParameters(na1, cmin, 1);
2908 for (i = 0; i < n; i++) {
2909 ptaGetIPt(pta, i, &x, &y);
2910 pixGetPixel(pixs, x, y, &val);
2911 numaAddNumber(na1, val);
2912 }
2913 array = numaGetFArray(na1, L_NOCOPY);
2914 ptaDestroy(&pta);
2915
2916 /* Compute root variance on overlapping windows */
2917 nad = numaCreate(n);
2918 *pnad = nad;
2919 numaSetParameters(nad, cmin + size / 2, 1);
2920 norm = 1.0f / (l_float32)size;
2921 for (i = 0; i < n - size; i++) { /* along the line */
2922 sum1 = sum2 = 0;
2923 for (j = 0; j < size; j++) { /* over the window */
2924 val = array[i + j];
2925 sum1 += val;
2926 sum2 += (l_float64)(val) * val;
2927 }
2928 ave = norm * sum1;
2929 var = norm * sum2 - ave * ave;
2930 if (var < 0) /* avoid small negative values from rounding effects */
2931 var = 0.0;
2932 rootvar = (l_float32)sqrt(var);
2933 numaAddNumber(nad, rootvar);
2934 }
2935
2936 numaDestroy(&na1);
2937 return 0;
2938 }
2939
2940
2941 /*---------------------------------------------------------------------*
2942 * Extract min/max of pixel values near lines *
2943 *---------------------------------------------------------------------*/
2944 /*!
2945 * \brief pixMinMaxNearLine()
2946 *
2947 * \param[in] pixs 8 bpp; no colormap
2948 * \param[in] x1, y1 starting pt for line
2949 * \param[in] x2, y2 end pt for line
2950 * \param[in] dist distance to search from line in each direction
2951 * \param[in] direction L_SCAN_NEGATIVE, L_SCAN_POSITIVE, L_SCAN_BOTH
2952 * \param[out] pnamin [optional] minimum values
2953 * \param[out] pnamax [optional] maximum values
2954 * \param[out] pminave [optional] average of minimum values
2955 * \param[out] pmaxave [optional] average of maximum values
2956 * \return 0 if OK; 1 on error or if there are no sampled points
2957 * within the image.
2958 *
2959 * <pre>
2960 * Notes:
2961 * (1) If the line is more horizontal than vertical, the values
2962 * are computed for [x1, x2], and the pixels are taken
2963 * below and/or above the local y-value. Otherwise, the
2964 * values are computed for [y1, y2] and the pixels are taken
2965 * to the left and/or right of the local x value.
2966 * (2) %direction specifies which side (or both sides) of the
2967 * line are scanned for min and max values.
2968 * (3) There are two ways to tell if the returned values of min
2969 * and max averages are valid: the returned values cannot be
2970 * negative and the function must return 0.
2971 * (4) All accessed pixels are clipped to the pix.
2972 * </pre>
2973 */
2974 l_ok
2975 pixMinMaxNearLine(PIX *pixs,
2976 l_int32 x1,
2977 l_int32 y1,
2978 l_int32 x2,
2979 l_int32 y2,
2980 l_int32 dist,
2981 l_int32 direction,
2982 NUMA **pnamin,
2983 NUMA **pnamax,
2984 l_float32 *pminave,
2985 l_float32 *pmaxave)
2986 {
2987 l_int32 i, j, w, h, d, x, y, n, dir, found, minval, maxval, negloc, posloc;
2988 l_uint32 val;
2989 l_float32 sum;
2990 NUMA *namin, *namax;
2991 PTA *pta;
2992
2993 if (pnamin) *pnamin = NULL;
2994 if (pnamax) *pnamax = NULL;
2995 if (pminave) *pminave = UNDEF;
2996 if (pmaxave) *pmaxave = UNDEF;
2997 if (!pnamin && !pnamax && !pminave && !pmaxave)
2998 return ERROR_INT("no output requested", __func__, 1);
2999 if (!pixs)
3000 return ERROR_INT("pixs not defined", __func__, 1);
3001 pixGetDimensions(pixs, &w, &h, &d);
3002 if (d != 8 || pixGetColormap(pixs))
3003 return ERROR_INT("pixs not 8 bpp or has colormap", __func__, 1);
3004 dist = L_ABS(dist);
3005 if (direction != L_SCAN_NEGATIVE && direction != L_SCAN_POSITIVE &&
3006 direction != L_SCAN_BOTH)
3007 return ERROR_INT("invalid direction", __func__, 1);
3008
3009 pta = generatePtaLine(x1, y1, x2, y2);
3010 n = ptaGetCount(pta);
3011 dir = (L_ABS(x1 - x2) == n - 1) ? L_HORIZ : L_VERT;
3012 namin = numaCreate(n);
3013 namax = numaCreate(n);
3014 negloc = -dist;
3015 posloc = dist;
3016 if (direction == L_SCAN_NEGATIVE)
3017 posloc = 0;
3018 else if (direction == L_SCAN_POSITIVE)
3019 negloc = 0;
3020 for (i = 0; i < n; i++) {
3021 ptaGetIPt(pta, i, &x, &y);
3022 minval = 255;
3023 maxval = 0;
3024 found = FALSE;
3025 if (dir == L_HORIZ) {
3026 if (x < 0 || x >= w) continue;
3027 for (j = negloc; j <= posloc; j++) {
3028 if (y + j < 0 || y + j >= h) continue;
3029 pixGetPixel(pixs, x, y + j, &val);
3030 found = TRUE;
3031 if (val < minval) minval = val;
3032 if (val > maxval) maxval = val;
3033 }
3034 } else { /* dir == L_VERT */
3035 if (y < 0 || y >= h) continue;
3036 for (j = negloc; j <= posloc; j++) {
3037 if (x + j < 0 || x + j >= w) continue;
3038 pixGetPixel(pixs, x + j, y, &val);
3039 found = TRUE;
3040 if (val < minval) minval = val;
3041 if (val > maxval) maxval = val;
3042 }
3043 }
3044 if (found) {
3045 numaAddNumber(namin, minval);
3046 numaAddNumber(namax, maxval);
3047 }
3048 }
3049
3050 n = numaGetCount(namin);
3051 if (n == 0) {
3052 numaDestroy(&namin);
3053 numaDestroy(&namax);
3054 ptaDestroy(&pta);
3055 return ERROR_INT("no output from this line", __func__, 1);
3056 }
3057
3058 if (pminave) {
3059 numaGetSum(namin, &sum);
3060 *pminave = sum / n;
3061 }
3062 if (pmaxave) {
3063 numaGetSum(namax, &sum);
3064 *pmaxave = sum / n;
3065 }
3066 if (pnamin)
3067 *pnamin = namin;
3068 else
3069 numaDestroy(&namin);
3070 if (pnamax)
3071 *pnamax = namax;
3072 else
3073 numaDestroy(&namax);
3074 ptaDestroy(&pta);
3075 return 0;
3076 }
3077
3078
3079 /*---------------------------------------------------------------------*
3080 * Rank row and column transforms *
3081 *---------------------------------------------------------------------*/
3082 /*!
3083 * \brief pixRankRowTransform()
3084 *
3085 * \param[in] pixs 8 bpp; no colormap
3086 * \return pixd with pixels sorted in each row, from
3087 * min to max value
3088 *
3089 * <pre>
3090 * Notes:
3091 * (1) The time is O(n) in the number of pixels and runs about
3092 * 100 Mpixels/sec on a 3 GHz machine.
3093 * </pre>
3094 */
3095 PIX *
3096 pixRankRowTransform(PIX *pixs)
3097 {
3098 l_int32 i, j, k, m, w, h, wpl, val;
3099 l_int32 histo[256];
3100 l_uint32 *datas, *datad, *lines, *lined;
3101 PIX *pixd;
3102
3103 if (!pixs)
3104 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
3105 if (pixGetDepth(pixs) != 8)
3106 return (PIX *)ERROR_PTR("pixs not 8 bpp", __func__, NULL);
3107 if (pixGetColormap(pixs))
3108 return (PIX *)ERROR_PTR("pixs has a colormap", __func__, NULL);
3109
3110 pixGetDimensions(pixs, &w, &h, NULL);
3111 pixd = pixCreateTemplate(pixs);
3112 datas = pixGetData(pixs);
3113 datad = pixGetData(pixd);
3114 wpl = pixGetWpl(pixs);
3115 for (i = 0; i < h; i++) {
3116 memset(histo, 0, 1024);
3117 lines = datas + i * wpl;
3118 lined = datad + i * wpl;
3119 for (j = 0; j < w; j++) {
3120 val = GET_DATA_BYTE(lines, j);
3121 histo[val]++;
3122 }
3123 for (m = 0, j = 0; m < 256; m++) {
3124 for (k = 0; k < histo[m]; k++, j++)
3125 SET_DATA_BYTE(lined, j, m);
3126 }
3127 }
3128
3129 return pixd;
3130 }
3131
3132
3133 /*!
3134 * \brief pixRankColumnTransform()
3135 *
3136 * \param[in] pixs 8 bpp; no colormap
3137 * \return pixd with pixels sorted in each column, from
3138 * min to max value
3139 *
3140 * <pre>
3141 * Notes:
3142 * (1) The time is O(n) in the number of pixels and runs about
3143 * 50 Mpixels/sec on a 3 GHz machine.
3144 * </pre>
3145 */
3146 PIX *
3147 pixRankColumnTransform(PIX *pixs)
3148 {
3149 l_int32 i, j, k, m, w, h, val;
3150 l_int32 histo[256];
3151 void **lines8, **lined8;
3152 PIX *pixd;
3153
3154 if (!pixs)
3155 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
3156 if (pixGetDepth(pixs) != 8)
3157 return (PIX *)ERROR_PTR("pixs not 8 bpp", __func__, NULL);
3158 if (pixGetColormap(pixs))
3159 return (PIX *)ERROR_PTR("pixs has a colormap", __func__, NULL);
3160
3161 pixGetDimensions(pixs, &w, &h, NULL);
3162 pixd = pixCreateTemplate(pixs);
3163 lines8 = pixGetLinePtrs(pixs, NULL);
3164 lined8 = pixGetLinePtrs(pixd, NULL);
3165 for (j = 0; j < w; j++) {
3166 memset(histo, 0, 1024);
3167 for (i = 0; i < h; i++) {
3168 val = GET_DATA_BYTE(lines8[i], j);
3169 histo[val]++;
3170 }
3171 for (m = 0, i = 0; m < 256; m++) {
3172 for (k = 0; k < histo[m]; k++, i++)
3173 SET_DATA_BYTE(lined8[i], j, m);
3174 }
3175 }
3176
3177 LEPT_FREE(lines8);
3178 LEPT_FREE(lined8);
3179 return pixd;
3180 }