comparison mupdf-source/thirdparty/leptonica/src/pix3.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 pix3.c
29 * <pre>
30 *
31 * This file has these operations:
32 *
33 * (1) Mask-directed operations
34 * (2) Full-image bit-logical operations
35 * (3) Foreground pixel counting operations on 1 bpp images
36 * (4) Average and variance of pixel values
37 * (5) Mirrored tiling of a smaller image
38 *
39 *
40 * Masked operations
41 * l_int32 pixSetMasked()
42 * l_int32 pixSetMaskedGeneral()
43 * l_int32 pixCombineMasked()
44 * l_int32 pixCombineMaskedGeneral()
45 * l_int32 pixPaintThroughMask()
46 * l_int32 pixCopyWithBoxa() -- this is boxa-directed
47 * PIX *pixPaintSelfThroughMask()
48 * PIX *pixMakeMaskFromVal()
49 * PIX *pixMakeMaskFromLUT()
50 * PIX *pixMakeArbMaskFromRGB()
51 * PIX *pixSetUnderTransparency()
52 * PIX *pixMakeAlphaFromMask()
53 * l_int32 pixGetColorNearMaskBoundary()
54 * PIX *pixDisplaySelectedPixels() -- for debugging
55 *
56 * One and two-image boolean operations on arbitrary depth images
57 * PIX *pixInvert()
58 * PIX *pixOr()
59 * PIX *pixAnd()
60 * PIX *pixXor()
61 * PIX *pixSubtract()
62 *
63 * Foreground pixel counting in 1 bpp images
64 * l_int32 pixZero()
65 * l_int32 pixForegroundFraction()
66 * NUMA *pixaCountPixels()
67 * l_int32 pixCountPixels()
68 * l_int32 pixCountPixelsInRect()
69 * NUMA *pixCountByRow()
70 * NUMA *pixCountByColumn()
71 * NUMA *pixCountPixelsByRow()
72 * NUMA *pixCountPixelsByColumn()
73 * l_int32 pixCountPixelsInRow()
74 * NUMA *pixGetMomentByColumn()
75 * l_int32 pixThresholdPixelSum()
76 * l_int32 *makePixelSumTab8()
77 * l_int32 *makePixelCentroidTab8()
78 *
79 * Average of pixel values in gray images
80 * NUMA *pixAverageByRow()
81 * NUMA *pixAverageByColumn()
82 * l_int32 pixAverageInRect()
83 *
84 * Average of pixel values in RGB images
85 * l_int32 pixAverageInRectRGB()
86 *
87 * Variance of pixel values in gray images
88 * NUMA *pixVarianceByRow()
89 * NUMA *pixVarianceByColumn()
90 * l_int32 pixVarianceInRect()
91 *
92 * Average of absolute value of pixel differences in gray images
93 * NUMA *pixAbsDiffByRow()
94 * NUMA *pixAbsDiffByColumn()
95 * l_int32 pixAbsDiffInRect()
96 * l_int32 pixAbsDiffOnLine()
97 *
98 * Count of pixels with specific value
99 * l_int32 pixCountArbInRect()
100 *
101 * Mirrored tiling
102 * PIX *pixMirroredTiling()
103 *
104 * Representative tile near but outside region
105 * l_int32 pixFindRepCloseTile()
106 *
107 * Static helper function
108 * static BOXA *findTileRegionsForSearch()
109 * </pre>
110 */
111
112 #ifdef HAVE_CONFIG_H
113 #include <config_auto.h>
114 #endif /* HAVE_CONFIG_H */
115
116 #include <string.h>
117 #include <math.h>
118 #include "allheaders.h"
119
120 static BOXA *findTileRegionsForSearch(BOX *box, l_int32 w, l_int32 h,
121 l_int32 searchdir, l_int32 mindist,
122 l_int32 tsize, l_int32 ntiles);
123
124 #ifndef NO_CONSOLE_IO
125 #define EQUAL_SIZE_WARNING 0
126 #endif /* ~NO_CONSOLE_IO */
127
128 /*-------------------------------------------------------------*
129 * Masked operations *
130 *-------------------------------------------------------------*/
131 /*!
132 * \brief pixSetMasked()
133 *
134 * \param[in] pixd 1, 2, 4, 8, 16 or 32 bpp; or colormapped
135 * \param[in] pixm [optional] 1 bpp mask; no operation if NULL
136 * \param[in] val value to set at each masked pixel
137 * \return 0 if OK; 1 on error
138 *
139 * <pre>
140 * Notes:
141 * (1) In-place operation.
142 * (2) NOTE: For cmapped images, this calls pixSetMaskedCmap().
143 * %val must be the 32-bit color representation of the RGB pixel.
144 * It is not the index into the colormap!
145 * (2) If pixm == NULL, a warning is given.
146 * (3) This is an implicitly aligned operation, where the UL
147 * corners of pixd and pixm coincide. A warning is
148 * issued if the two image sizes differ significantly,
149 * but the operation proceeds.
150 * (4) Each pixel in pixd that co-locates with an ON pixel
151 * in pixm is set to the specified input value.
152 * Other pixels in pixd are not changed.
153 * (5) You can visualize this as painting the color through
154 * the mask, as a stencil.
155 * (6) If you do not want to have the UL corners aligned,
156 * use the function pixSetMaskedGeneral(), which requires
157 * you to input the UL corner of pixm relative to pixd.
158 * (7) Implementation details: see comments in pixPaintThroughMask()
159 * for when we use rasterop to do the painting.
160 * </pre>
161 */
162 l_ok
163 pixSetMasked(PIX *pixd,
164 PIX *pixm,
165 l_uint32 val)
166 {
167 l_int32 wd, hd, wm, hm, w, h, d, wpld, wplm;
168 l_int32 i, j, rval, gval, bval;
169 l_uint32 *datad, *datam, *lined, *linem;
170
171 if (!pixd)
172 return ERROR_INT("pixd not defined", __func__, 1);
173 if (!pixm) {
174 L_WARNING("no mask; nothing to do\n", __func__);
175 return 0;
176 }
177 if (pixGetColormap(pixd)) {
178 extractRGBValues(val, &rval, &gval, &bval);
179 return pixSetMaskedCmap(pixd, pixm, 0, 0, rval, gval, bval);
180 }
181
182 if (pixGetDepth(pixm) != 1)
183 return ERROR_INT("pixm not 1 bpp", __func__, 1);
184 d = pixGetDepth(pixd);
185 if (d == 1)
186 val &= 1;
187 else if (d == 2)
188 val &= 3;
189 else if (d == 4)
190 val &= 0x0f;
191 else if (d == 8)
192 val &= 0xff;
193 else if (d == 16)
194 val &= 0xffff;
195 else if (d != 32)
196 return ERROR_INT("pixd not 1, 2, 4, 8, 16 or 32 bpp", __func__, 1);
197 pixGetDimensions(pixm, &wm, &hm, NULL);
198
199 /* If d == 1, use rasterop; it's about 25x faster */
200 if (d == 1) {
201 if (val == 0) {
202 PIX *pixmi = pixInvert(NULL, pixm);
203 pixRasterop(pixd, 0, 0, wm, hm, PIX_MASK, pixmi, 0, 0);
204 pixDestroy(&pixmi);
205 } else { /* val == 1 */
206 pixRasterop(pixd, 0, 0, wm, hm, PIX_PAINT, pixm, 0, 0);
207 }
208 return 0;
209 }
210
211 /* For d < 32, use rasterop for val == 0 (black); ~3x faster. */
212 if (d < 32 && val == 0) {
213 PIX *pixmd = pixUnpackBinary(pixm, d, 1);
214 pixRasterop(pixd, 0, 0, wm, hm, PIX_MASK, pixmd, 0, 0);
215 pixDestroy(&pixmd);
216 return 0;
217 }
218
219 /* For d < 32, use rasterop for val == maxval (white); ~3x faster. */
220 if (d < 32 && val == ((1 << d) - 1)) {
221 PIX *pixmd = pixUnpackBinary(pixm, d, 0);
222 pixRasterop(pixd, 0, 0, wm, hm, PIX_PAINT, pixmd, 0, 0);
223 pixDestroy(&pixmd);
224 return 0;
225 }
226
227 pixGetDimensions(pixd, &wd, &hd, &d);
228 w = L_MIN(wd, wm);
229 h = L_MIN(hd, hm);
230 if (L_ABS(wd - wm) > 7 || L_ABS(hd - hm) > 7) /* allow a small tolerance */
231 L_WARNING("pixd and pixm sizes differ\n", __func__);
232
233 datad = pixGetData(pixd);
234 datam = pixGetData(pixm);
235 wpld = pixGetWpl(pixd);
236 wplm = pixGetWpl(pixm);
237 for (i = 0; i < h; i++) {
238 lined = datad + i * wpld;
239 linem = datam + i * wplm;
240 for (j = 0; j < w; j++) {
241 if (GET_DATA_BIT(linem, j)) {
242 switch(d)
243 {
244 case 2:
245 SET_DATA_DIBIT(lined, j, val);
246 break;
247 case 4:
248 SET_DATA_QBIT(lined, j, val);
249 break;
250 case 8:
251 SET_DATA_BYTE(lined, j, val);
252 break;
253 case 16:
254 SET_DATA_TWO_BYTES(lined, j, val);
255 break;
256 case 32:
257 *(lined + j) = val;
258 break;
259 default:
260 return ERROR_INT("shouldn't get here", __func__, 1);
261 }
262 }
263 }
264 }
265
266 return 0;
267 }
268
269
270 /*!
271 * \brief pixSetMaskedGeneral()
272 *
273 * \param[in] pixd 8, 16 or 32 bpp
274 * \param[in] pixm [optional] 1 bpp mask; no operation if null
275 * \param[in] val value to set at each masked pixel
276 * \param[in] x, y location of UL corner of pixm relative to pixd;
277 * can be negative
278 * \return 0 if OK; 1 on error
279 *
280 * <pre>
281 * Notes:
282 * (1) This is an in-place operation.
283 * (2) Alignment is explicit. If you want the UL corners of
284 * the two images to be aligned, use pixSetMasked().
285 * (3) A typical use would be painting through the foreground
286 * of a small binary mask pixm, located somewhere on a
287 * larger pixd. Other pixels in pixd are not changed.
288 * (4) You can visualize this as painting the color through
289 * the mask, as a stencil.
290 * (5) This uses rasterop to handle clipping and different depths of pixd.
291 * (6) If pixd has a colormap, you should call pixPaintThroughMask().
292 * (7) Why is this function here, if pixPaintThroughMask() does the
293 * same thing, and does it more generally? I've retained it here
294 * to show how one can paint through a mask using only full
295 * image rasterops, rather than pixel peeking in pixm and poking
296 * in pixd. It's somewhat baroque, but I found it amusing.
297 * </pre>
298 */
299 l_ok
300 pixSetMaskedGeneral(PIX *pixd,
301 PIX *pixm,
302 l_uint32 val,
303 l_int32 x,
304 l_int32 y)
305 {
306 l_int32 wm, hm, d;
307 PIX *pixmu, *pixc;
308
309 if (!pixd)
310 return ERROR_INT("pixd not defined", __func__, 1);
311 if (!pixm) /* nothing to do */
312 return 0;
313
314 d = pixGetDepth(pixd);
315 if (d != 8 && d != 16 && d != 32)
316 return ERROR_INT("pixd not 8, 16 or 32 bpp", __func__, 1);
317 if (pixGetDepth(pixm) != 1)
318 return ERROR_INT("pixm not 1 bpp", __func__, 1);
319
320 /* Unpack binary to depth d, with inversion: 1 --> 0, 0 --> 0xff... */
321 if ((pixmu = pixUnpackBinary(pixm, d, 1)) == NULL)
322 return ERROR_INT("pixmu not made", __func__, 1);
323
324 /* Clear stenciled pixels in pixd */
325 pixGetDimensions(pixm, &wm, &hm, NULL);
326 pixRasterop(pixd, x, y, wm, hm, PIX_SRC & PIX_DST, pixmu, 0, 0);
327
328 /* Generate image with requisite color */
329 if ((pixc = pixCreateTemplate(pixmu)) == NULL) {
330 pixDestroy(&pixmu);
331 return ERROR_INT("pixc not made", __func__, 1);
332 }
333 pixSetAllArbitrary(pixc, val);
334
335 /* Invert stencil mask, and paint color color into stencil */
336 pixInvert(pixmu, pixmu);
337 pixAnd(pixmu, pixmu, pixc);
338
339 /* Finally, repaint stenciled pixels, with val, in pixd */
340 pixRasterop(pixd, x, y, wm, hm, PIX_SRC | PIX_DST, pixmu, 0, 0);
341
342 pixDestroy(&pixmu);
343 pixDestroy(&pixc);
344 return 0;
345 }
346
347
348 /*!
349 * \brief pixCombineMasked()
350 *
351 * \param[in] pixd 1 bpp, 8 bpp gray or 32 bpp rgb; no cmap
352 * \param[in] pixs 1 bpp, 8 bpp gray or 32 bpp rgb; no cmap
353 * \param[in] pixm [optional] 1 bpp mask; no operation if NULL
354 * \return 0 if OK; 1 on error
355 *
356 * <pre>
357 * Notes:
358 * (1) In-place operation; pixd is changed.
359 * (2) This sets each pixel in pixd that co-locates with an ON
360 * pixel in pixm to the corresponding value of pixs.
361 * (3) pixs and pixd must be the same depth and not colormapped.
362 * (4) All three input pix are aligned at the UL corner, and the
363 * operation is clipped to the intersection of all three images.
364 * (5) If pixm == NULL, it's a no-op.
365 * (6) Implementation: see notes in pixCombineMaskedGeneral().
366 * For 8 bpp selective masking, you might guess that it
367 * would be faster to generate an 8 bpp version of pixm,
368 * using pixConvert1To8(pixm, 0, 255), and then use a
369 * general combine operation
370 * d = (d & ~m) | (s & m)
371 * on a word-by-word basis. Not always. The word-by-word
372 * combine takes a time that is independent of the mask data.
373 * If the mask is relatively sparse, the byte-check method
374 * is actually faster!
375 * </pre>
376 */
377 l_ok
378 pixCombineMasked(PIX *pixd,
379 PIX *pixs,
380 PIX *pixm)
381 {
382 l_int32 w, h, d, ws, hs, ds, wm, hm, dm, wmin, hmin;
383 l_int32 wpl, wpls, wplm, i, j, val;
384 l_uint32 *data, *datas, *datam, *line, *lines, *linem;
385 PIX *pixt;
386
387 if (!pixm) /* nothing to do */
388 return 0;
389 if (!pixd)
390 return ERROR_INT("pixd not defined", __func__, 1);
391 if (!pixs)
392 return ERROR_INT("pixs not defined", __func__, 1);
393 pixGetDimensions(pixd, &w, &h, &d);
394 pixGetDimensions(pixs, &ws, &hs, &ds);
395 pixGetDimensions(pixm, &wm, &hm, &dm);
396 if (d != ds)
397 return ERROR_INT("pixs and pixd depths differ", __func__, 1);
398 if (dm != 1)
399 return ERROR_INT("pixm not 1 bpp", __func__, 1);
400 if (d != 1 && d != 8 && d != 32)
401 return ERROR_INT("pixd not 1, 8 or 32 bpp", __func__, 1);
402 if (pixGetColormap(pixd) || pixGetColormap(pixs))
403 return ERROR_INT("pixs and/or pixd is cmapped", __func__, 1);
404
405 /* For d = 1, use rasterop. pixt is the part from pixs, under
406 * the fg of pixm, that is to be combined with pixd. We also
407 * use pixt to remove all fg of pixd that is under the fg of pixm.
408 * Then pixt and pixd are combined by ORing. */
409 wmin = L_MIN(w, L_MIN(ws, wm));
410 hmin = L_MIN(h, L_MIN(hs, hm));
411 if (d == 1) {
412 pixt = pixAnd(NULL, pixs, pixm);
413 pixRasterop(pixd, 0, 0, wmin, hmin, PIX_DST & PIX_NOT(PIX_SRC),
414 pixm, 0, 0);
415 pixRasterop(pixd, 0, 0, wmin, hmin, PIX_SRC | PIX_DST, pixt, 0, 0);
416 pixDestroy(&pixt);
417 return 0;
418 }
419
420 data = pixGetData(pixd);
421 datas = pixGetData(pixs);
422 datam = pixGetData(pixm);
423 wpl = pixGetWpl(pixd);
424 wpls = pixGetWpl(pixs);
425 wplm = pixGetWpl(pixm);
426 if (d == 8) {
427 for (i = 0; i < hmin; i++) {
428 line = data + i * wpl;
429 lines = datas + i * wpls;
430 linem = datam + i * wplm;
431 for (j = 0; j < wmin; j++) {
432 if (GET_DATA_BIT(linem, j)) {
433 val = GET_DATA_BYTE(lines, j);
434 SET_DATA_BYTE(line, j, val);
435 }
436 }
437 }
438 } else { /* d == 32 */
439 for (i = 0; i < hmin; i++) {
440 line = data + i * wpl;
441 lines = datas + i * wpls;
442 linem = datam + i * wplm;
443 for (j = 0; j < wmin; j++) {
444 if (GET_DATA_BIT(linem, j))
445 line[j] = lines[j];
446 }
447 }
448 }
449
450 return 0;
451 }
452
453
454 /*!
455 * \brief pixCombineMaskedGeneral()
456 *
457 * \param[in] pixd 1 bpp, 8 bpp gray or 32 bpp rgb
458 * \param[in] pixs 1 bpp, 8 bpp gray or 32 bpp rgb
459 * \param[in] pixm [optional] 1 bpp mask
460 * \param[in] x, y origin of pixs and pixm relative to pixd; can be negative
461 * \return 0 if OK; 1 on error
462 *
463 * <pre>
464 * Notes:
465 * (1) In-place operation; pixd is changed.
466 * (2) This is a generalized version of pixCombinedMasked(), where
467 * the source and mask can be placed at the same (arbitrary)
468 * location relative to pixd.
469 * (3) pixs and pixd must be the same depth and not colormapped.
470 * (4) The UL corners of both pixs and pixm are aligned with
471 * the point (x, y) of pixd, and the operation is clipped to
472 * the intersection of all three images.
473 * (5) If pixm == NULL, it's a no-op.
474 * (6) Implementation. There are two ways to do these. In the first,
475 * we use rasterop, ORing the part of pixs under the mask
476 * with pixd (which has been appropriately cleared there first).
477 * In the second, the mask is used one pixel at a time to
478 * selectively replace pixels of pixd with those of pixs.
479 * Here, we use rasterop for 1 bpp and pixel-wise replacement
480 * for 8 and 32 bpp. To use rasterop for 8 bpp, for example,
481 * we must first generate an 8 bpp version of the mask.
482 * The code is simple:
483 *
484 * Pix *pixm8 = pixConvert1To8(NULL, pixm, 0, 255);
485 * Pix *pixt = pixAnd(NULL, pixs, pixm8);
486 * pixRasterop(pixd, x, y, wmin, hmin, PIX_DST & PIX_NOT(PIX_SRC),
487 * pixm8, 0, 0);
488 * pixRasterop(pixd, x, y, wmin, hmin, PIX_SRC | PIX_DST,
489 * pixt, 0, 0);
490 * pixDestroy(&pixt);
491 * pixDestroy(&pixm8);
492 * </pre>
493 */
494 l_ok
495 pixCombineMaskedGeneral(PIX *pixd,
496 PIX *pixs,
497 PIX *pixm,
498 l_int32 x,
499 l_int32 y)
500 {
501 l_int32 d, w, h, ws, hs, ds, wm, hm, dm, wmin, hmin;
502 l_int32 wpl, wpls, wplm, i, j, val;
503 l_uint32 *data, *datas, *datam, *line, *lines, *linem;
504 PIX *pixt;
505
506 if (!pixm) /* nothing to do */
507 return 0;
508 if (!pixd)
509 return ERROR_INT("pixd not defined", __func__, 1);
510 if (!pixs)
511 return ERROR_INT("pixs not defined", __func__, 1);
512 pixGetDimensions(pixd, &w, &h, &d);
513 pixGetDimensions(pixs, &ws, &hs, &ds);
514 pixGetDimensions(pixm, &wm, &hm, &dm);
515 if (d != ds)
516 return ERROR_INT("pixs and pixd depths differ", __func__, 1);
517 if (dm != 1)
518 return ERROR_INT("pixm not 1 bpp", __func__, 1);
519 if (d != 1 && d != 8 && d != 32)
520 return ERROR_INT("pixd not 1, 8 or 32 bpp", __func__, 1);
521 if (pixGetColormap(pixd) || pixGetColormap(pixs))
522 return ERROR_INT("pixs and/or pixd is cmapped", __func__, 1);
523
524 /* For d = 1, use rasterop. pixt is the part from pixs, under
525 * the fg of pixm, that is to be combined with pixd. We also
526 * use pixt to remove all fg of pixd that is under the fg of pixm.
527 * Then pixt and pixd are combined by ORing. */
528 wmin = L_MIN(ws, wm);
529 hmin = L_MIN(hs, hm);
530 if (d == 1) {
531 pixt = pixAnd(NULL, pixs, pixm);
532 pixRasterop(pixd, x, y, wmin, hmin, PIX_DST & PIX_NOT(PIX_SRC),
533 pixm, 0, 0);
534 pixRasterop(pixd, x, y, wmin, hmin, PIX_SRC | PIX_DST, pixt, 0, 0);
535 pixDestroy(&pixt);
536 return 0;
537 }
538
539 wpl = pixGetWpl(pixd);
540 data = pixGetData(pixd);
541 wpls = pixGetWpl(pixs);
542 datas = pixGetData(pixs);
543 wplm = pixGetWpl(pixm);
544 datam = pixGetData(pixm);
545
546 for (i = 0; i < hmin; i++) {
547 if (y + i < 0 || y + i >= h) continue;
548 line = data + (y + i) * wpl;
549 lines = datas + i * wpls;
550 linem = datam + i * wplm;
551 for (j = 0; j < wmin; j++) {
552 if (x + j < 0 || x + j >= w) continue;
553 if (GET_DATA_BIT(linem, j)) {
554 switch (d)
555 {
556 case 8:
557 val = GET_DATA_BYTE(lines, j);
558 SET_DATA_BYTE(line, x + j, val);
559 break;
560 case 32:
561 *(line + x + j) = *(lines + j);
562 break;
563 default:
564 return ERROR_INT("shouldn't get here", __func__, 1);
565 }
566 }
567 }
568 }
569
570 return 0;
571 }
572
573
574 /*!
575 * \brief pixPaintThroughMask()
576 *
577 * \param[in] pixd 1, 2, 4, 8, 16 or 32 bpp; or colormapped
578 * \param[in] pixm [optional] 1 bpp mask
579 * \param[in] x, y origin of pixm relative to pixd; can be negative
580 * \param[in] val pixel value to set at each masked pixel
581 * \return 0 if OK; 1 on error
582 *
583 * <pre>
584 * Notes:
585 * (1) In-place operation. Calls pixSetMaskedCmap() for colormapped
586 * images.
587 * (2) For 1, 2, 4, 8 and 16 bpp gray, we take the appropriate
588 * number of least significant bits of val.
589 * (3) If pixm == NULL, it's a no-op.
590 * (4) The mask origin is placed at (x,y) on pixd, and the
591 * operation is clipped to the intersection of rectangles.
592 * (5) For rgb, the components in val are in the canonical locations,
593 * with red in location COLOR_RED, etc.
594 * (6) Implementation detail 1:
595 * For painting with val == 0 or val == maxval, you can use rasterop.
596 * If val == 0, invert the mask so that it's 0 over the region
597 * into which you want to write, and use PIX_SRC & PIX_DST to
598 * clear those pixels. To write with val = maxval (all 1's),
599 * use PIX_SRC | PIX_DST to set all bits under the mask.
600 * (7) Implementation detail 2:
601 * The rasterop trick can be used for depth > 1 as well.
602 * For val == 0, generate the mask for depth d from the binary
603 * mask using
604 * pixmd = pixUnpackBinary(pixm, d, 1);
605 * and use pixRasterop() with PIX_MASK. For val == maxval,
606 * pixmd = pixUnpackBinary(pixm, d, 0);
607 * and use pixRasterop() with PIX_PAINT.
608 * But note that if d == 32 bpp, it is about 3x faster to use
609 * the general implementation (not pixRasterop()).
610 * (8) Implementation detail 3:
611 * It might be expected that the switch in the inner loop will
612 * cause large branching delays and should be avoided.
613 * This is not the case, because the entrance is always the
614 * same and the compiler can correctly predict the jump.
615 * </pre>
616 */
617 l_ok
618 pixPaintThroughMask(PIX *pixd,
619 PIX *pixm,
620 l_int32 x,
621 l_int32 y,
622 l_uint32 val)
623 {
624 l_int32 d, w, h, wm, hm, wpl, wplm, i, j, rval, gval, bval;
625 l_uint32 *data, *datam, *line, *linem;
626
627 if (!pixm) /* nothing to do */
628 return 0;
629 if (!pixd)
630 return ERROR_INT("pixd not defined", __func__, 1);
631 if (pixGetColormap(pixd)) {
632 extractRGBValues(val, &rval, &gval, &bval);
633 return pixSetMaskedCmap(pixd, pixm, x, y, rval, gval, bval);
634 }
635
636 if (pixGetDepth(pixm) != 1)
637 return ERROR_INT("pixm not 1 bpp", __func__, 1);
638 d = pixGetDepth(pixd);
639 if (d == 1)
640 val &= 1;
641 else if (d == 2)
642 val &= 3;
643 else if (d == 4)
644 val &= 0x0f;
645 else if (d == 8)
646 val &= 0xff;
647 else if (d == 16)
648 val &= 0xffff;
649 else if (d != 32)
650 return ERROR_INT("pixd not 1, 2, 4, 8, 16 or 32 bpp", __func__, 1);
651 pixGetDimensions(pixm, &wm, &hm, NULL);
652
653 /* If d == 1, use rasterop; it's about 25x faster. */
654 if (d == 1) {
655 if (val == 0) {
656 PIX *pixmi = pixInvert(NULL, pixm);
657 pixRasterop(pixd, x, y, wm, hm, PIX_MASK, pixmi, 0, 0);
658 pixDestroy(&pixmi);
659 } else { /* val == 1 */
660 pixRasterop(pixd, x, y, wm, hm, PIX_PAINT, pixm, 0, 0);
661 }
662 return 0;
663 }
664
665 /* For d < 32, use rasterop if val == 0 (black); ~3x faster. */
666 if (d < 32 && val == 0) {
667 PIX *pixmd = pixUnpackBinary(pixm, d, 1);
668 pixRasterop(pixd, x, y, wm, hm, PIX_MASK, pixmd, 0, 0);
669 pixDestroy(&pixmd);
670 return 0;
671 }
672
673 /* For d < 32, use rasterop if val == maxval (white); ~3x faster. */
674 if (d < 32 && val == ((1 << d) - 1)) {
675 PIX *pixmd = pixUnpackBinary(pixm, d, 0);
676 pixRasterop(pixd, x, y, wm, hm, PIX_PAINT, pixmd, 0, 0);
677 pixDestroy(&pixmd);
678 return 0;
679 }
680
681 /* All other cases */
682 pixGetDimensions(pixd, &w, &h, NULL);
683 wpl = pixGetWpl(pixd);
684 data = pixGetData(pixd);
685 wplm = pixGetWpl(pixm);
686 datam = pixGetData(pixm);
687 for (i = 0; i < hm; i++) {
688 if (y + i < 0 || y + i >= h) continue;
689 line = data + (y + i) * wpl;
690 linem = datam + i * wplm;
691 for (j = 0; j < wm; j++) {
692 if (x + j < 0 || x + j >= w) continue;
693 if (GET_DATA_BIT(linem, j)) {
694 switch (d)
695 {
696 case 2:
697 SET_DATA_DIBIT(line, x + j, val);
698 break;
699 case 4:
700 SET_DATA_QBIT(line, x + j, val);
701 break;
702 case 8:
703 SET_DATA_BYTE(line, x + j, val);
704 break;
705 case 16:
706 SET_DATA_TWO_BYTES(line, x + j, val);
707 break;
708 case 32:
709 *(line + x + j) = val;
710 break;
711 default:
712 return ERROR_INT("shouldn't get here", __func__, 1);
713 }
714 }
715 }
716 }
717
718 return 0;
719 }
720
721
722 /*!
723 * \brief pixCopyWithBoxa()
724 *
725 * \param[in] pixs all depths; cmap ok
726 * \param[in] boxa e.g., from components of a photomask
727 * \param[in] background L_SET_WHITE or L_SET_BLACK
728 * \return pixd or NULL on error
729 *
730 * <pre>
731 * Notes:
732 * (1) Pixels from pixs are copied ("blitted") through each box into pixd.
733 * (2) Pixels not copied are preset to either white or black.
734 * (3) This fast and simple implementation can use rasterop because
735 * each region to be copied is rectangular.
736 * (4) A much slower implementation that doesn't use rasterop would make
737 * a 1 bpp mask from the boxa and then copy, pixel by pixel,
738 * through the mask:
739 * pixGetDimensions(pixs, &w, &h, NULL);
740 * pixm = pixCreate(w, h, 1);
741 * pixm = pixMaskBoxa(pixm, pixm, boxa);
742 * pixd = pixCreateTemplate(pixs);
743 * pixSetBlackOrWhite(pixd, background);
744 * pixCombineMasked(pixd, pixs, pixm);
745 * pixDestroy(&pixm);
746 * </pre>
747 */
748 PIX *
749 pixCopyWithBoxa(PIX *pixs,
750 BOXA *boxa,
751 l_int32 background)
752 {
753 l_int32 i, n, x, y, w, h;
754 PIX *pixd;
755
756 if (!pixs)
757 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
758 if (!boxa)
759 return (PIX *)ERROR_PTR("boxa not defined", __func__, NULL);
760 if (background != L_SET_WHITE && background != L_SET_BLACK)
761 return (PIX *)ERROR_PTR("invalid background", __func__, NULL);
762
763 pixd = pixCreateTemplate(pixs);
764 pixSetBlackOrWhite(pixd, background);
765 n = boxaGetCount(boxa);
766 for (i = 0; i < n; i++) {
767 boxaGetBoxGeometry(boxa, i, &x, &y, &w, &h);
768 pixRasterop(pixd, x, y, w, h, PIX_SRC, pixs, x, y);
769 }
770 return pixd;
771 }
772
773
774 /*!
775 * \brief pixPaintSelfThroughMask()
776 *
777 * \param[in] pixd 8 bpp gray or 32 bpp rgb; not colormapped
778 * \param[in] pixm 1 bpp mask
779 * \param[in] x, y origin of pixm relative to pixd; must not be negative
780 * \param[in] searchdir L_HORIZ, L_VERT or L_BOTH_DIRECTIONS
781 * \param[in] mindist min distance of nearest tile edge to box; >= 0
782 * \param[in] tilesize requested size for tiling; may be reduced
783 * \param[in] ntiles number of tiles tested in each row/column
784 * \param[in] distblend distance outside the fg used for blending with pixs
785 * \return 0 if OK; 1 on error
786 *
787 * <pre>
788 * Notes:
789 * (1) In-place operation; pixd is changed.
790 * (2) If pixm == NULL, it's a no-op.
791 * (3) The mask origin is placed at (x,y) on pixd, and the
792 * operation is clipped to the intersection of pixd and the
793 * fg of the mask.
794 * (4) %tsize is the the requested size for tiling. The actual
795 * actual size for each c.c. will be bounded by the minimum
796 * dimension of the c.c.
797 * (5) For %mindist, %searchdir and %ntiles, see pixFindRepCloseTile().
798 * They determine the set of possible tiles that can be used
799 * to build a larger mirrored tile to paint onto pixd through
800 * the c.c. of pixm.
801 * (6) %distblend is used for alpha blending. It is only applied
802 * if there is exactly one c.c. in the mask. Use distblend == 0
803 * to skip blending and just paint through the 1 bpp mask.
804 * (7) To apply blending to more than 1 component, call this function
805 * repeatedly with %pixm, %x and %y representing one component of
806 * the mask each time. This would be done as follows, for an
807 * underlying image pixs and mask pixm of components to fill:
808 * Boxa *boxa = pixConnComp(pixm, &pixa, 8);
809 * n = boxaGetCount(boxa);
810 * for (i = 0; i < n; i++) {
811 * Pix *pix = pixaGetPix(pixa, i, L_CLONE);
812 * Box *box = pixaGetBox(pixa, i, L_CLONE);
813 * boxGetGeometry(box, &bx, &by, &bw, &bh);
814 * pixPaintSelfThroughMask(pixs, pix, bx, by, searchdir,
815 * mindist, tilesize, ntiles, distblend);
816 * pixDestroy(&pix);
817 * boxDestroy(&box);
818 * }
819 * pixaDestroy(&pixa);
820 * boxaDestroy(&boxa);
821 * (8) If no tiles can be found, this falls back to estimating the
822 * color near the boundary of the region to be textured.
823 * (9) This can be used to replace the pixels in some regions of
824 * an image by selected neighboring pixels. The mask represents
825 * the pixels to be replaced. For each connected component in
826 * the mask, this function selects up to two tiles of neighboring
827 * pixels to be used for replacement of pixels represented by
828 * the component (i.e., under the FG of that component in the mask).
829 * After selection, mirror replication is used to generate an
830 * image that is large enough to cover the component. Alpha
831 * blending can also be used outside of the component, but near the
832 * edge, to blur the transition between painted and original pixels.
833 * </pre>
834 */
835 l_ok
836 pixPaintSelfThroughMask(PIX *pixd,
837 PIX *pixm,
838 l_int32 x,
839 l_int32 y,
840 l_int32 searchdir,
841 l_int32 mindist,
842 l_int32 tilesize,
843 l_int32 ntiles,
844 l_int32 distblend)
845 {
846 l_int32 w, h, d, wm, hm, dm, i, n, bx, by, bw, bh, edgeblend, retval, minside;
847 l_uint32 pixval;
848 BOX *box, *boxv, *boxh;
849 BOXA *boxa;
850 PIX *pixf, *pixv, *pixh, *pix1, *pix2, *pix3, *pix4, *pix5;
851 PIXA *pixa;
852
853 if (!pixm) /* nothing to do */
854 return 0;
855 if (!pixd)
856 return ERROR_INT("pixd not defined", __func__, 1);
857 if (pixGetColormap(pixd) != NULL)
858 return ERROR_INT("pixd has colormap", __func__, 1);
859 pixGetDimensions(pixd, &w, &h, &d);
860 if (d != 8 && d != 32)
861 return ERROR_INT("pixd not 8 or 32 bpp", __func__, 1);
862 pixGetDimensions(pixm, &wm, &hm, &dm);
863 if (dm != 1)
864 return ERROR_INT("pixm not 1 bpp", __func__, 1);
865 if (x < 0 || y < 0)
866 return ERROR_INT("x and y must be non-negative", __func__, 1);
867 if (searchdir != L_HORIZ && searchdir != L_VERT &&
868 searchdir != L_BOTH_DIRECTIONS)
869 return ERROR_INT("invalid searchdir", __func__, 1);
870 if (tilesize < 2)
871 return ERROR_INT("tilesize must be >= 2", __func__, 1);
872 if (distblend < 0)
873 return ERROR_INT("distblend must be >= 0", __func__, 1);
874
875 /* Embed mask in full sized mask */
876 if (wm < w || hm < h) {
877 pixf = pixCreate(w, h, 1);
878 pixRasterop(pixf, x, y, wm, hm, PIX_SRC, pixm, 0, 0);
879 } else {
880 pixf = pixCopy(NULL, pixm);
881 }
882
883 /* Get connected components of mask */
884 boxa = pixConnComp(pixf, &pixa, 8);
885 if ((n = pixaGetCount(pixa)) == 0) {
886 L_WARNING("no fg in mask\n", __func__);
887 pixDestroy(&pixf);
888 pixaDestroy(&pixa);
889 boxaDestroy(&boxa);
890 return 1;
891 }
892 boxaDestroy(&boxa);
893
894 /* For each c.c., generate one or two representative tiles for
895 * texturizing and apply through the mask. The input 'tilesize'
896 * is the requested value. Note that if there is exactly one
897 * component, and blending at the edge is requested, an alpha mask
898 * is generated, which is larger than the bounding box of the c.c. */
899 edgeblend = (n == 1 && distblend > 0) ? 1 : 0;
900 if (distblend > 0 && n > 1)
901 L_WARNING("%d components; can not blend at edges\n", __func__, n);
902 retval = 0;
903 for (i = 0; i < n; i++) {
904 if (edgeblend) {
905 pix1 = pixMakeAlphaFromMask(pixf, distblend, &box);
906 } else {
907 pix1 = pixaGetPix(pixa, i, L_CLONE);
908 box = pixaGetBox(pixa, i, L_CLONE);
909 }
910 boxGetGeometry(box, &bx, &by, &bw, &bh);
911 minside = L_MIN(bw, bh);
912
913 boxh = boxv = NULL;
914 if (searchdir == L_HORIZ || searchdir == L_BOTH_DIRECTIONS) {
915 pixFindRepCloseTile(pixd, box, L_HORIZ, mindist,
916 L_MIN(minside, tilesize), ntiles, &boxh, 0);
917 }
918 if (searchdir == L_VERT || searchdir == L_BOTH_DIRECTIONS) {
919 pixFindRepCloseTile(pixd, box, L_VERT, mindist,
920 L_MIN(minside, tilesize), ntiles, &boxv, 0);
921 }
922 if (!boxh && !boxv) {
923 L_WARNING("tile region not selected; paint color near boundary\n",
924 __func__);
925 pixDestroy(&pix1);
926 pix1 = pixaGetPix(pixa, i, L_CLONE);
927 pixaGetBoxGeometry(pixa, i, &bx, &by, NULL, NULL);
928 retval = pixGetColorNearMaskBoundary(pixd, pixm, box, distblend,
929 &pixval, 0);
930 pixSetMaskedGeneral(pixd, pix1, pixval, bx, by);
931 pixDestroy(&pix1);
932 boxDestroy(&box);
933 continue;
934 }
935
936 /* Extract the selected squares from pixd */
937 pixh = (boxh) ? pixClipRectangle(pixd, boxh, NULL) : NULL;
938 pixv = (boxv) ? pixClipRectangle(pixd, boxv, NULL) : NULL;
939 if (pixh && pixv)
940 pix2 = pixBlend(pixh, pixv, 0, 0, 0.5);
941 else if (pixh)
942 pix2 = pixClone(pixh);
943 else /* pixv */
944 pix2 = pixClone(pixv);
945 pixDestroy(&pixh);
946 pixDestroy(&pixv);
947 boxDestroy(&boxh);
948 boxDestroy(&boxv);
949
950 /* Generate an image the size of the b.b. of the c.c.,
951 * possibly extended by the blending distance, which
952 * is then either painted through the c.c. mask or
953 * blended using the alpha mask for that c.c. */
954 pix3 = pixMirroredTiling(pix2, bw, bh);
955 if (edgeblend) {
956 pix4 = pixClipRectangle(pixd, box, NULL);
957 pix5 = pixBlendWithGrayMask(pix4, pix3, pix1, 0, 0);
958 pixRasterop(pixd, bx, by, bw, bh, PIX_SRC, pix5, 0, 0);
959 pixDestroy(&pix4);
960 pixDestroy(&pix5);
961 } else {
962 pixCombineMaskedGeneral(pixd, pix3, pix1, bx, by);
963 }
964 pixDestroy(&pix1);
965 pixDestroy(&pix2);
966 pixDestroy(&pix3);
967 boxDestroy(&box);
968 }
969
970 pixaDestroy(&pixa);
971 pixDestroy(&pixf);
972 return retval;
973 }
974
975
976 /*!
977 * \brief pixMakeMaskFromVal()
978 *
979 * \param[in] pixs 2, 4 or 8 bpp; can be colormapped
980 * \param[in] val pixel value
981 * \return pixd 1 bpp mask, or NULL on error
982 *
983 * <pre>
984 * Notes:
985 * (1) This generates a 1 bpp mask image, where a 1 is written in
986 * the mask for each pixel in pixs that has a value %val.
987 * (2) If no pixels have the value, an empty mask is generated.
988 * </pre>
989 */
990 PIX *
991 pixMakeMaskFromVal(PIX *pixs,
992 l_int32 val)
993 {
994 l_int32 w, h, d, i, j, sval, wpls, wpld;
995 l_uint32 *datas, *datad, *lines, *lined;
996 PIX *pixd;
997
998 if (!pixs)
999 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
1000 pixGetDimensions(pixs, &w, &h, &d);
1001 if (d != 2 && d != 4 && d != 8)
1002 return (PIX *)ERROR_PTR("pix not 2, 4 or 8 bpp", __func__, NULL);
1003
1004 pixd = pixCreate(w, h, 1);
1005 pixCopyResolution(pixd, pixs);
1006 pixCopyInputFormat(pixd, pixs);
1007 datas = pixGetData(pixs);
1008 datad = pixGetData(pixd);
1009 wpls = pixGetWpl(pixs);
1010 wpld = pixGetWpl(pixd);
1011 for (i = 0; i < h; i++) {
1012 lines = datas + i * wpls;
1013 lined = datad + i * wpld;
1014 for (j = 0; j < w; j++) {
1015 if (d == 2)
1016 sval = GET_DATA_DIBIT(lines, j);
1017 else if (d == 4)
1018 sval = GET_DATA_QBIT(lines, j);
1019 else /* d == 8 */
1020 sval = GET_DATA_BYTE(lines, j);
1021 if (sval == val)
1022 SET_DATA_BIT(lined, j);
1023 }
1024 }
1025
1026 return pixd;
1027 }
1028
1029
1030 /*!
1031 * \brief pixMakeMaskFromLUT()
1032 *
1033 * \param[in] pixs 2, 4 or 8 bpp; can be colormapped
1034 * \param[in] tab 256-entry LUT; 1 means to write to mask
1035 * \return pixd 1 bpp mask, or NULL on error
1036 *
1037 * <pre>
1038 * Notes:
1039 * (1) This generates a 1 bpp mask image, where a 1 is written in
1040 * the mask for each pixel in pixs that has a value corresponding
1041 * to a 1 in the LUT.
1042 * (2) The LUT should be of size 256.
1043 * </pre>
1044 */
1045 PIX *
1046 pixMakeMaskFromLUT(PIX *pixs,
1047 l_int32 *tab)
1048 {
1049 l_int32 w, h, d, i, j, val, wpls, wpld;
1050 l_uint32 *datas, *datad, *lines, *lined;
1051 PIX *pixd;
1052
1053 if (!pixs)
1054 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
1055 if (!tab)
1056 return (PIX *)ERROR_PTR("tab not defined", __func__, NULL);
1057 pixGetDimensions(pixs, &w, &h, &d);
1058 if (d != 2 && d != 4 && d != 8)
1059 return (PIX *)ERROR_PTR("pix not 2, 4 or 8 bpp", __func__, NULL);
1060
1061 pixd = pixCreate(w, h, 1);
1062 pixCopyResolution(pixd, pixs);
1063 pixCopyInputFormat(pixd, pixs);
1064 datas = pixGetData(pixs);
1065 datad = pixGetData(pixd);
1066 wpls = pixGetWpl(pixs);
1067 wpld = pixGetWpl(pixd);
1068 for (i = 0; i < h; i++) {
1069 lines = datas + i * wpls;
1070 lined = datad + i * wpld;
1071 for (j = 0; j < w; j++) {
1072 if (d == 2)
1073 val = GET_DATA_DIBIT(lines, j);
1074 else if (d == 4)
1075 val = GET_DATA_QBIT(lines, j);
1076 else /* d == 8 */
1077 val = GET_DATA_BYTE(lines, j);
1078 if (tab[val] == 1)
1079 SET_DATA_BIT(lined, j);
1080 }
1081 }
1082
1083 return pixd;
1084 }
1085
1086
1087 /*!
1088 * \brief pixMakeArbMaskFromRGB()
1089 *
1090 * \param[in] pixs 32 bpp RGB
1091 * \param[in] rc, gc, bc arithmetic factors; can be negative
1092 * \param[in] thresh lower threshold on weighted sum of components
1093 * \return pixd 1 bpp mask, or NULL on error
1094 *
1095 * <pre>
1096 * Notes:
1097 * (1) This generates a 1 bpp mask image, where a 1 is written in
1098 * the mask for each pixel in pixs that satisfies
1099 * rc * rval + gc * gval + bc * bval > thresh
1100 * where rval is the red component, etc.
1101 * (2) Unlike with pixConvertToGray(), there are no constraints
1102 * on the color coefficients, which can be negative. For
1103 * example, a mask that discriminates against red and in favor
1104 * of blue will have rc < 0.0 and bc > 0.0.
1105 * (3) To make the result independent of intensity (the 'V' in HSV),
1106 * select coefficients so that %thresh = 0. Then the result
1107 * is not changed when all components are multiplied by the
1108 * same constant (as long as nothing saturates). This can be
1109 * useful if, for example, the illumination is not uniform.
1110 * </pre>
1111 */
1112 PIX *
1113 pixMakeArbMaskFromRGB(PIX *pixs,
1114 l_float32 rc,
1115 l_float32 gc,
1116 l_float32 bc,
1117 l_float32 thresh)
1118 {
1119 PIX *pix1, *pix2;
1120
1121 if (!pixs || pixGetDepth(pixs) != 32)
1122 return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", __func__, NULL);
1123 if (thresh >= 255.0) thresh = 254.0; /* avoid 8 bit overflow */
1124
1125 if ((pix1 = pixConvertRGBToGrayArb(pixs, rc, gc, bc)) == NULL)
1126 return (PIX *)ERROR_PTR("pix1 not made", __func__, NULL);
1127 pix2 = pixThresholdToBinary(pix1, thresh + 1);
1128 pixInvert(pix2, pix2);
1129 pixDestroy(&pix1);
1130 return pix2;
1131 }
1132
1133
1134 /*!
1135 * \brief pixSetUnderTransparency()
1136 *
1137 * \param[in] pixs 32 bpp rgba
1138 * \param[in] val 32 bit unsigned color to use where alpha == 0
1139 * \param[in] debug displays layers of pixs
1140 * \return pixd 32 bpp rgba, or NULL on error
1141 *
1142 * <pre>
1143 * Notes:
1144 * (1) This sets the r, g and b components under every fully
1145 * transparent alpha component to %val. The alpha components
1146 * are unchanged.
1147 * (2) Full transparency is denoted by alpha == 0. Setting
1148 * all pixels to a constant %val where alpha is transparent
1149 * can improve compressibility by reducing the entropy.
1150 * (3) The visual result depends on how the image is displayed.
1151 * (a) For display devices that respect the use of the alpha
1152 * layer, this will not affect the appearance.
1153 * (b) For typical leptonica operations, alpha is ignored,
1154 * so there will be a change in appearance because this
1155 * resets the rgb values in the fully transparent region.
1156 * (4) pixRead() and pixWrite() will, by default, read and write
1157 * 4-component (rgba) pix in png format. To ignore the alpha
1158 * component after reading, or omit it on writing, pixSetSpp(..., 3).
1159 * (5) Here are some examples:
1160 * * To convert all fully transparent pixels in a 4 component
1161 * (rgba) png file to white:
1162 * pixs = pixRead(<infile>);
1163 * pixd = pixSetUnderTransparency(pixs, 0xffffff00, 0);
1164 * * To write pixd with the alpha component:
1165 * pixWrite(<outfile>, pixd, IFF_PNG);
1166 * * To write and rgba image without the alpha component, first do:
1167 * pixSetSpp(pixd, 3);
1168 * If you later want to use the alpha, spp must be reset to 4.
1169 * * (fancier) To remove the alpha by blending the image over
1170 * a white background:
1171 * pixRemoveAlpha()
1172 * This changes all pixel values where the alpha component is
1173 * not opaque (255).
1174 * (6) Caution. rgb images in leptonica typically have value 0 in
1175 * the alpha channel, which is fully transparent. If spp for
1176 * such an image were changed from 3 to 4, the image becomes
1177 * fully transparent, and this function will set each pixel to %val.
1178 * If you really want to set every pixel to the same value,
1179 * use pixSetAllArbitrary().
1180 * (7) This is useful for compressing an RGBA image where the part
1181 * of the image that is fully transparent is random junk; compression
1182 * is typically improved by setting that region to a constant.
1183 * For rendering as a 3 component RGB image over a uniform
1184 * background of arbitrary color, use pixAlphaBlendUniform().
1185 * </pre>
1186 */
1187 PIX *
1188 pixSetUnderTransparency(PIX *pixs,
1189 l_uint32 val,
1190 l_int32 debug)
1191 {
1192 PIX *pixg, *pixm, *pixt, *pixd;
1193
1194 if (!pixs || pixGetDepth(pixs) != 32)
1195 return (PIX *)ERROR_PTR("pixs not defined or not 32 bpp",
1196 __func__, NULL);
1197
1198 if (pixGetSpp(pixs) != 4) {
1199 L_WARNING("no alpha channel; returning a copy\n", __func__);
1200 return pixCopy(NULL, pixs);
1201 }
1202
1203 /* Make a mask from the alpha component with ON pixels
1204 * wherever the alpha component is fully transparent (0).
1205 * The hard way:
1206 * l_int32 *lut = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
1207 * lut[0] = 1;
1208 * pixg = pixGetRGBComponent(pixs, L_ALPHA_CHANNEL);
1209 * pixm = pixMakeMaskFromLUT(pixg, lut);
1210 * LEPT_FREE(lut);
1211 * But there's an easier way to set pixels in a mask where
1212 * the alpha component is 0 ... */
1213 pixg = pixGetRGBComponent(pixs, L_ALPHA_CHANNEL);
1214 pixm = pixThresholdToBinary(pixg, 1);
1215
1216 if (debug) {
1217 pixt = pixDisplayLayersRGBA(pixs, 0xffffff00, 600);
1218 pixDisplay(pixt, 0, 0);
1219 pixDestroy(&pixt);
1220 }
1221
1222 pixd = pixCopy(NULL, pixs);
1223 pixSetMasked(pixd, pixm, (val & 0xffffff00));
1224 pixDestroy(&pixg);
1225 pixDestroy(&pixm);
1226 return pixd;
1227 }
1228
1229
1230 /*!
1231 * \brief pixMakeAlphaFromMask()
1232 *
1233 * \param[in] pixs 1 bpp
1234 * \param[in] dist blending distance; typically 10 - 30
1235 * \param[out] pbox [optional] use NULL to get the full size
1236 * \return pixd (8 bpp gray, or NULL on error
1237 *
1238 * <pre>
1239 * Notes:
1240 * (1) This generates a 8 bpp alpha layer that is opaque (256)
1241 * over the FG of pixs, and goes transparent linearly away
1242 * from the FG pixels, decaying to 0 (transparent) is an
1243 * 8-connected distance given by %dist. If %dist == 0,
1244 * this does a simple conversion from 1 to 8 bpp.
1245 * (2) If &box == NULL, this returns an alpha mask that is the
1246 * full size of pixs. Otherwise, the returned mask pixd covers
1247 * just the FG pixels of pixs, expanded by %dist in each
1248 * direction (if possible), and the returned box gives the
1249 * location of the returned mask relative to pixs.
1250 * (3) This is useful for painting through a mask and allowing
1251 * blending of the painted image with an underlying image
1252 * in the mask background for pixels near foreground mask pixels.
1253 * For example, with an underlying rgb image pix1, an overlaying
1254 * image rgb pix2, binary mask pixm, and dist > 0, this
1255 * blending is achieved with:
1256 * pix3 = pixMakeAlphaFromMask(pixm, dist, &box);
1257 * boxGetGeometry(box, &x, &y, NULL, NULL);
1258 * pix4 = pixBlendWithGrayMask(pix1, pix2, pix3, x, y);
1259 * </pre>
1260 */
1261 PIX *
1262 pixMakeAlphaFromMask(PIX *pixs,
1263 l_int32 dist,
1264 BOX **pbox)
1265 {
1266 l_int32 w, h;
1267 BOX *box1, *box2;
1268 PIX *pix1, *pixd;
1269
1270 if (pbox) *pbox = NULL;
1271 if (!pixs || pixGetDepth(pixs) != 1)
1272 return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL);
1273 if (dist < 0)
1274 return (PIX *)ERROR_PTR("dist must be >= 0", __func__, NULL);
1275
1276 /* If requested, extract just the region to be affected by the mask */
1277 if (pbox) {
1278 pixClipToForeground(pixs, NULL, &box1);
1279 if (!box1) {
1280 L_WARNING("no ON pixels in mask\n", __func__);
1281 return pixCreateTemplate(pixs); /* all background (0) */
1282 }
1283
1284 boxAdjustSides(box1, box1, -dist, dist, -dist, dist);
1285 pixGetDimensions(pixs, &w, &h, NULL);
1286 box2 = boxClipToRectangle(box1, w, h);
1287 *pbox = box2;
1288 pix1 = pixClipRectangle(pixs, box2, NULL);
1289 boxDestroy(&box1);
1290 } else {
1291 pix1 = pixCopy(NULL, pixs);
1292 }
1293
1294 if (dist == 0) {
1295 pixd = pixConvert1To8(NULL, pix1, 0, 255);
1296 pixDestroy(&pix1);
1297 return pixd;
1298 }
1299
1300 /* Blur the boundary of the input mask */
1301 pixInvert(pix1, pix1);
1302 pixd = pixDistanceFunction(pix1, 8, 8, L_BOUNDARY_FG);
1303 pixMultConstantGray(pixd, 256.0f / dist);
1304 pixInvert(pixd, pixd);
1305 pixDestroy(&pix1);
1306 return pixd;
1307 }
1308
1309
1310 /*!
1311 * \brief pixGetColorNearMaskBoundary()
1312 *
1313 * \param[in] pixs 32 bpp rgb
1314 * \param[in] pixm 1 bpp mask, full image
1315 * \param[in] box region of mask; typically b.b. of a component
1316 * \param[in] dist distance into BG from mask boundary to use
1317 * \param[out] pval average pixel value
1318 * \param[in] debug 1 to output mask images
1319 * \return 0 if OK, 1 on error.
1320 *
1321 * <pre>
1322 * Notes:
1323 * (1) This finds the average color in a set of pixels that are
1324 * roughly a distance %dist from the c.c. boundary and in the
1325 * background of the mask image.
1326 * </pre>
1327 */
1328 l_ok
1329 pixGetColorNearMaskBoundary(PIX *pixs,
1330 PIX *pixm,
1331 BOX *box,
1332 l_int32 dist,
1333 l_uint32 *pval,
1334 l_int32 debug)
1335 {
1336 char op[64];
1337 l_int32 empty, bx, by;
1338 l_float32 rval, gval, bval;
1339 BOX *box1, *box2;
1340 PIX *pix1, *pix2, *pix3;
1341
1342 if (!pval)
1343 return ERROR_INT("&pval not defined", __func__, 1);
1344 *pval = 0xffffff00; /* white */
1345 if (!pixs || pixGetDepth(pixs) != 32)
1346 return ERROR_INT("pixs undefined or not 32 bpp", __func__, 1);
1347 if (!pixm || pixGetDepth(pixm) != 1)
1348 return ERROR_INT("pixm undefined or not 1 bpp", __func__, 1);
1349 if (!box)
1350 return ERROR_INT("box not defined", __func__, 1);
1351 if (dist < 0)
1352 return ERROR_INT("dist must be >= 0", __func__, 1);
1353
1354 /* Clip mask piece, expanded beyond %box by (%dist + 5) on each side.
1355 * box1 is the region requested; box2 is the actual region retrieved,
1356 * which is clipped to %pixm */
1357 box1 = boxAdjustSides(NULL, box, -dist - 5, dist + 5, -dist - 5, dist + 5);
1358 pix1 = pixClipRectangle(pixm, box1, &box2);
1359
1360 /* Expand FG by %dist into the BG */
1361 if (dist == 0) {
1362 pix2 = pixCopy(NULL, pix1);
1363 } else {
1364 snprintf(op, sizeof(op), "d%d.%d", 2 * dist, 2 * dist);
1365 pix2 = pixMorphSequence(pix1, op, 0);
1366 }
1367
1368 /* Expand again by 5 pixels on all sides (dilate 11x11) and XOR,
1369 * getting the annulus of FG pixels between %dist and %dist + 5 */
1370 pix3 = pixCopy(NULL, pix2);
1371 pixDilateBrick(pix3, pix3, 11, 11);
1372 pixXor(pix3, pix3, pix2);
1373 pixZero(pix3, &empty);
1374 if (!empty) {
1375 /* Scan the same region in %pixs, to get average under FG in pix3 */
1376 boxGetGeometry(box2, &bx, &by, NULL, NULL);
1377 pixGetAverageMaskedRGB(pixs, pix3, bx, by, 1, L_MEAN_ABSVAL,
1378 &rval, &gval, &bval);
1379 composeRGBPixel((l_int32)(rval + 0.5), (l_int32)(gval + 0.5),
1380 (l_int32)(bval + 0.5), pval);
1381 } else {
1382 L_WARNING("no pixels found\n", __func__);
1383 }
1384
1385 if (debug) {
1386 lept_rmdir("masknear"); /* erase previous images */
1387 lept_mkdir("masknear");
1388 pixWriteDebug("/tmp/masknear/input.png", pix1, IFF_PNG);
1389 pixWriteDebug("/tmp/masknear/adjusted.png", pix2, IFF_PNG);
1390 pixWriteDebug("/tmp/masknear/outerfive.png", pix3, IFF_PNG);
1391 lept_stderr("Input box; with adjusted sides; clipped\n");
1392 boxPrintStreamInfo(stderr, box);
1393 boxPrintStreamInfo(stderr, box1);
1394 boxPrintStreamInfo(stderr, box2);
1395 }
1396
1397 pixDestroy(&pix1);
1398 pixDestroy(&pix2);
1399 pixDestroy(&pix3);
1400 boxDestroy(&box1);
1401 boxDestroy(&box2);
1402 return 0;
1403 }
1404
1405
1406 /*!
1407 * \brief pixDisplaySelectedPixels()
1408 *
1409 * \param[in] pixs [optional] any depth
1410 * \param[in] pixm 1 bpp mask, aligned UL corner with %pixs
1411 * \param[in] sel [optional] pattern to paint at each pixel in pixm
1412 * \param[in] val rgb rendering of pattern
1413 * \return pixd, or NULL on error
1414 *
1415 * <pre>
1416 * Notes:
1417 * (1) For every fg pixel in %pixm, this paints the pattern in %sel
1418 * in color %val on a copy of %pixs.
1419 * (2) The implementation is to dilate %pixm by %sel, and then
1420 * paint through the dilated mask onto %pixs.
1421 * (3) If %pixs == NULL, it paints on a white image.
1422 * (4) If %sel == NULL, it paints only the pixels in the input %pixm.
1423 * (5) This visualization would typically be used in debugging.
1424 * </pre>
1425 */
1426 PIX *
1427 pixDisplaySelectedPixels(PIX *pixs,
1428 PIX *pixm,
1429 SEL *sel,
1430 l_uint32 val)
1431 {
1432 l_int32 w, h;
1433 PIX *pix1, *pix2;
1434
1435 if (!pixm || pixGetDepth(pixm) != 1)
1436 return (PIX *)ERROR_PTR("pixm undefined or not 1 bpp", __func__, NULL);
1437
1438 if (pixs) {
1439 pix1 = pixConvertTo32(pixs);
1440 } else {
1441 pixGetDimensions(pixm, &w, &h, NULL);
1442 pix1 = pixCreate(w, h, 32);
1443 pixSetAll(pix1);
1444 }
1445
1446 if (sel)
1447 pix2 = pixDilate(NULL, pixm, sel);
1448 else
1449 pix2 = pixClone(pixm);
1450 pixSetMasked(pix1, pix2, val);
1451 pixDestroy(&pix2);
1452 return pix1;
1453 }
1454
1455
1456 /*-------------------------------------------------------------*
1457 * One and two-image boolean ops on arbitrary depth images *
1458 *-------------------------------------------------------------*/
1459 /*!
1460 * \brief pixInvert()
1461 *
1462 * \param[in] pixd [optional]; this can be null, equal to pixs,
1463 * or different from pixs
1464 * \param[in] pixs
1465 * \return pixd, or NULL on error
1466 *
1467 * <pre>
1468 * Notes:
1469 * (1) This inverts pixs, for all pixel depths.
1470 * (2) There are 3 cases:
1471 * (a) pixd == null, ~src --> new pixd
1472 * (b) pixd == pixs, ~src --> src (in-place)
1473 * (c) pixd != pixs, ~src --> input pixd
1474 * (3) For clarity, if the case is known, use these patterns:
1475 * (a) pixd = pixInvert(NULL, pixs);
1476 * (b) pixInvert(pixs, pixs);
1477 * (c) pixInvert(pixd, pixs);
1478 * </pre>
1479 */
1480 PIX *
1481 pixInvert(PIX *pixd,
1482 PIX *pixs)
1483 {
1484 if (!pixs)
1485 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
1486
1487 /* Prepare pixd for in-place operation */
1488 if ((pixd = pixCopy(pixd, pixs)) == NULL)
1489 return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
1490
1491 pixRasterop(pixd, 0, 0, pixGetWidth(pixd), pixGetHeight(pixd),
1492 PIX_NOT(PIX_DST), NULL, 0, 0); /* invert pixd */
1493
1494 return pixd;
1495 }
1496
1497
1498 /*!
1499 * \brief pixOr()
1500 *
1501 * \param[in] pixd [optional]; this can be null, equal to pixs1,
1502 * different from pixs1
1503 * \param[in] pixs1 can be == pixd
1504 * \param[in] pixs2 must be != pixd
1505 * \return pixd always
1506 *
1507 * <pre>
1508 * Notes:
1509 * (1) This gives the union of two images with equal depth,
1510 * aligning them to the UL corner. pixs1 and pixs2
1511 * need not have the same width and height.
1512 * (2) There are 3 cases:
1513 * (a) pixd == null, (src1 | src2) --> new pixd
1514 * (b) pixd == pixs1, (src1 | src2) --> src1 (in-place)
1515 * (c) pixd != pixs1, (src1 | src2) --> input pixd
1516 * (3) For clarity, if the case is known, use these patterns:
1517 * (a) pixd = pixOr(NULL, pixs1, pixs2);
1518 * (b) pixOr(pixs1, pixs1, pixs2);
1519 * (c) pixOr(pixd, pixs1, pixs2);
1520 * (4) The size of the result is determined by pixs1.
1521 * (5) The depths of pixs1 and pixs2 must be equal.
1522 * (6) Note carefully that the order of pixs1 and pixs2 only matters
1523 * for the in-place case. For in-place, you must have
1524 * pixd == pixs1. Setting pixd == pixs2 gives an incorrect
1525 * result: the copy puts pixs1 image data in pixs2, and
1526 * the rasterop is then between pixs2 and pixs2 (a no-op).
1527 * </pre>
1528 */
1529 PIX *
1530 pixOr(PIX *pixd,
1531 PIX *pixs1,
1532 PIX *pixs2)
1533 {
1534 if (!pixs1)
1535 return (PIX *)ERROR_PTR("pixs1 not defined", __func__, pixd);
1536 if (!pixs2)
1537 return (PIX *)ERROR_PTR("pixs2 not defined", __func__, pixd);
1538 if (pixd == pixs2)
1539 return (PIX *)ERROR_PTR("cannot have pixs2 == pixd", __func__, pixd);
1540 if (pixGetDepth(pixs1) != pixGetDepth(pixs2))
1541 return (PIX *)ERROR_PTR("depths of pixs* unequal", __func__, pixd);
1542
1543 #if EQUAL_SIZE_WARNING
1544 if (!pixSizesEqual(pixs1, pixs2))
1545 L_WARNING("pixs1 and pixs2 not equal sizes\n", __func__);
1546 #endif /* EQUAL_SIZE_WARNING */
1547
1548 /* Prepare pixd to be a copy of pixs1 */
1549 if ((pixd = pixCopy(pixd, pixs1)) == NULL)
1550 return (PIX *)ERROR_PTR("pixd not made", __func__, pixd);
1551
1552 /* src1 | src2 --> dest */
1553 pixRasterop(pixd, 0, 0, pixGetWidth(pixd), pixGetHeight(pixd),
1554 PIX_SRC | PIX_DST, pixs2, 0, 0);
1555
1556 return pixd;
1557 }
1558
1559
1560 /*!
1561 * \brief pixAnd()
1562 *
1563 * \param[in] pixd [optional]; this can be null, equal to pixs1,
1564 * different from pixs1
1565 * \param[in] pixs1 can be == pixd
1566 * \param[in] pixs2 must be != pixd
1567 * \return pixd always
1568 *
1569 * <pre>
1570 * Notes:
1571 * (1) This gives the intersection of two images with equal depth,
1572 * aligning them to the the UL corner. pixs1 and pixs2
1573 * need not have the same width and height.
1574 * (2) There are 3 cases:
1575 * (a) pixd == null, (src1 & src2) --> new pixd
1576 * (b) pixd == pixs1, (src1 & src2) --> src1 (in-place)
1577 * (c) pixd != pixs1, (src1 & src2) --> input pixd
1578 * (3) For clarity, if the case is known, use these patterns:
1579 * (a) pixd = pixAnd(NULL, pixs1, pixs2);
1580 * (b) pixAnd(pixs1, pixs1, pixs2);
1581 * (c) pixAnd(pixd, pixs1, pixs2);
1582 * (4) The size of the result is determined by pixs1.
1583 * (5) The depths of pixs1 and pixs2 must be equal.
1584 * (6) Note carefully that the order of pixs1 and pixs2 only matters
1585 * for the in-place case. For in-place, you must have
1586 * pixd == pixs1. Setting pixd == pixs2 gives an incorrect
1587 * result: the copy puts pixs1 image data in pixs2, and
1588 * the rasterop is then between pixs2 and pixs2 (a no-op).
1589 * </pre>
1590 */
1591 PIX *
1592 pixAnd(PIX *pixd,
1593 PIX *pixs1,
1594 PIX *pixs2)
1595 {
1596 if (!pixs1)
1597 return (PIX *)ERROR_PTR("pixs1 not defined", __func__, pixd);
1598 if (!pixs2)
1599 return (PIX *)ERROR_PTR("pixs2 not defined", __func__, pixd);
1600 if (pixd == pixs2)
1601 return (PIX *)ERROR_PTR("cannot have pixs2 == pixd", __func__, pixd);
1602 if (pixGetDepth(pixs1) != pixGetDepth(pixs2))
1603 return (PIX *)ERROR_PTR("depths of pixs* unequal", __func__, pixd);
1604
1605 #if EQUAL_SIZE_WARNING
1606 if (!pixSizesEqual(pixs1, pixs2))
1607 L_WARNING("pixs1 and pixs2 not equal sizes\n", __func__);
1608 #endif /* EQUAL_SIZE_WARNING */
1609
1610 /* Prepare pixd to be a copy of pixs1 */
1611 if ((pixd = pixCopy(pixd, pixs1)) == NULL)
1612 return (PIX *)ERROR_PTR("pixd not made", __func__, pixd);
1613
1614 /* src1 & src2 --> dest */
1615 pixRasterop(pixd, 0, 0, pixGetWidth(pixd), pixGetHeight(pixd),
1616 PIX_SRC & PIX_DST, pixs2, 0, 0);
1617
1618 return pixd;
1619 }
1620
1621
1622 /*!
1623 * \brief pixXor()
1624 *
1625 * \param[in] pixd [optional]; this can be null, equal to pixs1,
1626 * different from pixs1
1627 * \param[in] pixs1 can be == pixd
1628 * \param[in] pixs2 must be != pixd
1629 * \return pixd always
1630 *
1631 * <pre>
1632 * Notes:
1633 * (1) This gives the XOR of two images with equal depth,
1634 * aligning them to the the UL corner. pixs1 and pixs2
1635 * need not have the same width and height.
1636 * (2) There are 3 cases:
1637 * (a) pixd == null, (src1 ^ src2) --> new pixd
1638 * (b) pixd == pixs1, (src1 ^ src2) --> src1 (in-place)
1639 * (c) pixd != pixs1, (src1 ^ src2) --> input pixd
1640 * (3) For clarity, if the case is known, use these patterns:
1641 * (a) pixd = pixXor(NULL, pixs1, pixs2);
1642 * (b) pixXor(pixs1, pixs1, pixs2);
1643 * (c) pixXor(pixd, pixs1, pixs2);
1644 * (4) The size of the result is determined by pixs1.
1645 * (5) The depths of pixs1 and pixs2 must be equal.
1646 * (6) Note carefully that the order of pixs1 and pixs2 only matters
1647 * for the in-place case. For in-place, you must have
1648 * pixd == pixs1. Setting pixd == pixs2 gives an incorrect
1649 * result: the copy puts pixs1 image data in pixs2, and
1650 * the rasterop is then between pixs2 and pixs2 (a no-op).
1651 * </pre>
1652 */
1653 PIX *
1654 pixXor(PIX *pixd,
1655 PIX *pixs1,
1656 PIX *pixs2)
1657 {
1658 if (!pixs1)
1659 return (PIX *)ERROR_PTR("pixs1 not defined", __func__, pixd);
1660 if (!pixs2)
1661 return (PIX *)ERROR_PTR("pixs2 not defined", __func__, pixd);
1662 if (pixd == pixs2)
1663 return (PIX *)ERROR_PTR("cannot have pixs2 == pixd", __func__, pixd);
1664 if (pixGetDepth(pixs1) != pixGetDepth(pixs2))
1665 return (PIX *)ERROR_PTR("depths of pixs* unequal", __func__, pixd);
1666
1667 #if EQUAL_SIZE_WARNING
1668 if (!pixSizesEqual(pixs1, pixs2))
1669 L_WARNING("pixs1 and pixs2 not equal sizes\n", __func__);
1670 #endif /* EQUAL_SIZE_WARNING */
1671
1672 /* Prepare pixd to be a copy of pixs1 */
1673 if ((pixd = pixCopy(pixd, pixs1)) == NULL)
1674 return (PIX *)ERROR_PTR("pixd not made", __func__, pixd);
1675
1676 /* src1 ^ src2 --> dest */
1677 pixRasterop(pixd, 0, 0, pixGetWidth(pixd), pixGetHeight(pixd),
1678 PIX_SRC ^ PIX_DST, pixs2, 0, 0);
1679
1680 return pixd;
1681 }
1682
1683
1684 /*!
1685 * \brief pixSubtract()
1686 *
1687 * \param[in] pixd [optional]; this can be null, equal to pixs1,
1688 * equal to pixs2, or different from both pixs1 and pixs2
1689 * \param[in] pixs1 can be == pixd
1690 * \param[in] pixs2 can be == pixd
1691 * \return pixd always
1692 *
1693 * <pre>
1694 * Notes:
1695 * (1) This gives the set subtraction of two images with equal depth,
1696 * aligning them to the the UL corner. pixs1 and pixs2
1697 * need not have the same width and height.
1698 * (2) Source pixs2 is always subtracted from source pixs1.
1699 * The result is
1700 * pixs1 \ pixs2 = pixs1 & (~pixs2)
1701 * (3) There are 4 cases:
1702 * (a) pixd == null, (src1 - src2) --> new pixd
1703 * (b) pixd == pixs1, (src1 - src2) --> src1 (in-place)
1704 * (c) pixd == pixs2, (src1 - src2) --> src2 (in-place)
1705 * (d) pixd != pixs1 && pixd != pixs2),
1706 * (src1 - src2) --> input pixd
1707 * (4) For clarity, if the case is known, use these patterns:
1708 * (a) pixd = pixSubtract(NULL, pixs1, pixs2);
1709 * (b) pixSubtract(pixs1, pixs1, pixs2);
1710 * (c) pixSubtract(pixs2, pixs1, pixs2);
1711 * (d) pixSubtract(pixd, pixs1, pixs2);
1712 * (5) The size of the result is determined by pixs1.
1713 * (6) The depths of pixs1 and pixs2 must be equal.
1714 * </pre>
1715 */
1716 PIX *
1717 pixSubtract(PIX *pixd,
1718 PIX *pixs1,
1719 PIX *pixs2)
1720 {
1721 l_int32 w, h;
1722
1723 if (!pixs1)
1724 return (PIX *)ERROR_PTR("pixs1 not defined", __func__, pixd);
1725 if (!pixs2)
1726 return (PIX *)ERROR_PTR("pixs2 not defined", __func__, pixd);
1727 if (pixGetDepth(pixs1) != pixGetDepth(pixs2))
1728 return (PIX *)ERROR_PTR("depths of pixs* unequal", __func__, pixd);
1729
1730 #if EQUAL_SIZE_WARNING
1731 if (!pixSizesEqual(pixs1, pixs2))
1732 L_WARNING("pixs1 and pixs2 not equal sizes\n", __func__);
1733 #endif /* EQUAL_SIZE_WARNING */
1734
1735 pixGetDimensions(pixs1, &w, &h, NULL);
1736 if (!pixd) {
1737 pixd = pixCopy(NULL, pixs1);
1738 pixRasterop(pixd, 0, 0, w, h, PIX_DST & PIX_NOT(PIX_SRC),
1739 pixs2, 0, 0); /* src1 & (~src2) */
1740 } else if (pixd == pixs1) {
1741 pixRasterop(pixd, 0, 0, w, h, PIX_DST & PIX_NOT(PIX_SRC),
1742 pixs2, 0, 0); /* src1 & (~src2) */
1743 } else if (pixd == pixs2) {
1744 pixRasterop(pixd, 0, 0, w, h, PIX_NOT(PIX_DST) & PIX_SRC,
1745 pixs1, 0, 0); /* src1 & (~src2) */
1746 } else { /* pixd != pixs1 && pixd != pixs2 */
1747 pixCopy(pixd, pixs1); /* sizes pixd to pixs1 if unequal */
1748 pixRasterop(pixd, 0, 0, w, h, PIX_DST & PIX_NOT(PIX_SRC),
1749 pixs2, 0, 0); /* src1 & (~src2) */
1750 }
1751
1752 return pixd;
1753 }
1754
1755
1756 /*-------------------------------------------------------------*
1757 * Pixel counting *
1758 *-------------------------------------------------------------*/
1759 /*!
1760 * \brief pixZero()
1761 *
1762 * \param[in] pix all depths; colormap OK
1763 * \param[out] pempty 1 if all bits in image data field are 0; 0 otherwise
1764 * \return 0 if OK; 1 on error
1765 *
1766 * <pre>
1767 * Notes:
1768 * (1) For a binary image, if there are no fg (black) pixels, empty = 1.
1769 * (2) For a grayscale image, if all pixels are black (0), empty = 1.
1770 * (3) For an RGB image, if all 4 components in every pixel is 0
1771 * (i.e. opaque black), empty = 1.
1772 * (4) For a colormapped image, pixel values are 0. The colormap
1773 * is ignored.
1774 * </pre>
1775 */
1776 l_ok
1777 pixZero(PIX *pix,
1778 l_int32 *pempty)
1779 {
1780 l_int32 w, h, wpl, i, j, fullwords, endbits;
1781 l_uint32 endmask;
1782 l_uint32 *data, *line;
1783
1784 if (!pempty)
1785 return ERROR_INT("&empty not defined", __func__, 1);
1786 *pempty = 1;
1787 if (!pix)
1788 return ERROR_INT("pix not defined", __func__, 1);
1789
1790 w = pixGetWidth(pix) * pixGetDepth(pix); /* in bits */
1791 h = pixGetHeight(pix);
1792 wpl = pixGetWpl(pix);
1793 data = pixGetData(pix);
1794 fullwords = w / 32;
1795 endbits = w & 31;
1796 endmask = (endbits == 0) ? 0 : (0xffffffffU << (32 - endbits));
1797
1798 for (i = 0; i < h; i++) {
1799 line = data + wpl * i;
1800 for (j = 0; j < fullwords; j++)
1801 if (*line++) {
1802 *pempty = 0;
1803 return 0;
1804 }
1805 if (endbits) {
1806 if (*line & endmask) {
1807 *pempty = 0;
1808 return 0;
1809 }
1810 }
1811 }
1812
1813 return 0;
1814 }
1815
1816
1817 /*!
1818 * \brief pixForegroundFraction()
1819 *
1820 * \param[in] pix 1 bpp
1821 * \param[out] pfract fraction of ON pixels
1822 * \return 0 if OK; 1 on error
1823 */
1824 l_ok
1825 pixForegroundFraction(PIX *pix,
1826 l_float32 *pfract)
1827 {
1828 l_int32 w, h, count;
1829
1830 if (!pfract)
1831 return ERROR_INT("&fract not defined", __func__, 1);
1832 *pfract = 0.0;
1833 if (!pix || pixGetDepth(pix) != 1)
1834 return ERROR_INT("pix not defined or not 1 bpp", __func__, 1);
1835
1836 pixCountPixels(pix, &count, NULL);
1837 pixGetDimensions(pix, &w, &h, NULL);
1838 *pfract = (l_float32)count / (l_float32)(w * h);
1839 return 0;
1840 }
1841
1842
1843 /*!
1844 * \brief pixaCountPixels()
1845 *
1846 * \param[in] pixa array of 1 bpp pix
1847 * \return na of ON pixels in each pix, or NULL on error
1848 */
1849 NUMA *
1850 pixaCountPixels(PIXA *pixa)
1851 {
1852 l_int32 d, i, n, count;
1853 l_int32 *tab;
1854 NUMA *na;
1855 PIX *pix;
1856
1857 if (!pixa)
1858 return (NUMA *)ERROR_PTR("pix not defined", __func__, NULL);
1859
1860 if ((n = pixaGetCount(pixa)) == 0)
1861 return numaCreate(1);
1862
1863 pix = pixaGetPix(pixa, 0, L_CLONE);
1864 d = pixGetDepth(pix);
1865 pixDestroy(&pix);
1866 if (d != 1)
1867 return (NUMA *)ERROR_PTR("pixa not 1 bpp", __func__, NULL);
1868
1869 if ((na = numaCreate(n)) == NULL)
1870 return (NUMA *)ERROR_PTR("na not made", __func__, NULL);
1871 tab = makePixelSumTab8();
1872 for (i = 0; i < n; i++) {
1873 pix = pixaGetPix(pixa, i, L_CLONE);
1874 pixCountPixels(pix, &count, tab);
1875 numaAddNumber(na, count);
1876 pixDestroy(&pix);
1877 }
1878
1879 LEPT_FREE(tab);
1880 return na;
1881 }
1882
1883
1884 /*!
1885 * \brief pixCountPixels()
1886 *
1887 * \param[in] pixs 1 bpp
1888 * \param[out] pcount count of ON pixels
1889 * \param[in] tab8 [optional] 8-bit pixel lookup table
1890 * \return 0 if OK; 1 on error
1891 */
1892 l_ok
1893 pixCountPixels(PIX *pixs,
1894 l_int32 *pcount,
1895 l_int32 *tab8)
1896 {
1897 l_uint32 endmask;
1898 l_int32 w, h, wpl, i, j;
1899 l_int32 fullwords, endbits, sum;
1900 l_int32 *tab;
1901 l_uint32 *data;
1902
1903 if (!pcount)
1904 return ERROR_INT("&count not defined", __func__, 1);
1905 *pcount = 0;
1906 if (!pixs || pixGetDepth(pixs) != 1)
1907 return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1);
1908
1909 tab = (tab8) ? tab8 : makePixelSumTab8();
1910 pixGetDimensions(pixs, &w, &h, NULL);
1911 wpl = pixGetWpl(pixs);
1912 data = pixGetData(pixs);
1913 fullwords = w >> 5;
1914 endbits = w & 31;
1915 endmask = (endbits == 0) ? 0 : (0xffffffffU << (32 - endbits));
1916
1917 sum = 0;
1918 for (i = 0; i < h; i++, data += wpl) {
1919 for (j = 0; j < fullwords; j++) {
1920 l_uint32 word = data[j];
1921 if (word) {
1922 sum += tab[word & 0xff] +
1923 tab[(word >> 8) & 0xff] +
1924 tab[(word >> 16) & 0xff] +
1925 tab[(word >> 24) & 0xff];
1926 }
1927 }
1928 if (endbits) {
1929 l_uint32 word = data[j] & endmask;
1930 if (word) {
1931 sum += tab[word & 0xff] +
1932 tab[(word >> 8) & 0xff] +
1933 tab[(word >> 16) & 0xff] +
1934 tab[(word >> 24) & 0xff];
1935 }
1936 }
1937 }
1938 *pcount = sum;
1939
1940 if (!tab8) LEPT_FREE(tab);
1941 return 0;
1942 }
1943
1944
1945 /*!
1946 * \brief pixCountPixelsInRect()
1947 *
1948 * \param[in] pixs 1 bpp
1949 * \param[in] box (can be null)
1950 * \param[out] pcount count of ON pixels
1951 * \param[in] tab8 [optional] 8-bit pixel lookup table
1952 * \return 0 if OK; 1 on error
1953 */
1954 l_ok
1955 pixCountPixelsInRect(PIX *pixs,
1956 BOX *box,
1957 l_int32 *pcount,
1958 l_int32 *tab8)
1959 {
1960 l_int32 w, h, bx, by, bw, bh;
1961 BOX *box1;
1962 PIX *pix1;
1963
1964 if (!pcount)
1965 return ERROR_INT("&count not defined", __func__, 1);
1966 *pcount = 0;
1967 if (!pixs || pixGetDepth(pixs) != 1)
1968 return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1);
1969
1970 if (box) {
1971 pixGetDimensions(pixs, &w, &h, NULL);
1972 if ((box1 = boxClipToRectangle(box, w, h)) == NULL)
1973 return ERROR_INT("box1 not made", __func__, 1);
1974 boxGetGeometry(box1, &bx, &by, &bw, &bh);
1975 pix1 = pixCreate(bw, bh, 1);
1976 pixRasterop(pix1, 0, 0, bw, bh, PIX_SRC, pixs, bx, by);
1977 pixCountPixels(pix1, pcount, tab8);
1978 pixDestroy(&pix1);
1979 boxDestroy(&box1);
1980 } else {
1981 pixCountPixels(pixs, pcount, tab8);
1982 }
1983
1984 return 0;
1985 }
1986
1987
1988 /*!
1989 * \brief pixCountByRow()
1990 *
1991 * \param[in] pix 1 bpp
1992 * \param[in] box [optional] clipping box for count; can be null
1993 * \return na of number of ON pixels by row, or NULL on error
1994 *
1995 * <pre>
1996 * Notes:
1997 * (1) To resample for a bin size different from 1, use
1998 * numaUniformSampling() on the result of this function.
1999 * </pre>
2000 */
2001 NUMA *
2002 pixCountByRow(PIX *pix,
2003 BOX *box)
2004 {
2005 l_int32 i, j, w, h, wpl, count, xstart, xend, ystart, yend, bw, bh;
2006 l_uint32 *line, *data;
2007 NUMA *na;
2008
2009 if (!pix || pixGetDepth(pix) != 1)
2010 return (NUMA *)ERROR_PTR("pix undefined or not 1 bpp", __func__, NULL);
2011 if (!box)
2012 return pixCountPixelsByRow(pix, NULL);
2013
2014 pixGetDimensions(pix, &w, &h, NULL);
2015 if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
2016 &bw, &bh) == 1)
2017 return (NUMA *)ERROR_PTR("invalid clipping box", __func__, NULL);
2018
2019 if ((na = numaCreate(bh)) == NULL)
2020 return (NUMA *)ERROR_PTR("na not made", __func__, NULL);
2021 numaSetParameters(na, ystart, 1);
2022 data = pixGetData(pix);
2023 wpl = pixGetWpl(pix);
2024 for (i = ystart; i < yend; i++) {
2025 count = 0;
2026 line = data + i * wpl;
2027 for (j = xstart; j < xend; j++) {
2028 if (GET_DATA_BIT(line, j))
2029 count++;
2030 }
2031 numaAddNumber(na, count);
2032 }
2033
2034 return na;
2035 }
2036
2037
2038 /*!
2039 * \brief pixCountByColumn()
2040 *
2041 * \param[in] pix 1 bpp
2042 * \param[in] box [optional] clipping box for count; can be null
2043 * \return na of number of ON pixels by column, or NULL on error
2044 *
2045 * <pre>
2046 * Notes:
2047 * (1) To resample for a bin size different from 1, use
2048 * numaUniformSampling() on the result of this function.
2049 * </pre>
2050 */
2051 NUMA *
2052 pixCountByColumn(PIX *pix,
2053 BOX *box)
2054 {
2055 l_int32 i, j, w, h, wpl, count, xstart, xend, ystart, yend, bw, bh;
2056 l_uint32 *line, *data;
2057 NUMA *na;
2058
2059 if (!pix || pixGetDepth(pix) != 1)
2060 return (NUMA *)ERROR_PTR("pix undefined or not 1 bpp", __func__, NULL);
2061 if (!box)
2062 return pixCountPixelsByColumn(pix);
2063
2064 pixGetDimensions(pix, &w, &h, NULL);
2065 if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
2066 &bw, &bh) == 1)
2067 return (NUMA *)ERROR_PTR("invalid clipping box", __func__, NULL);
2068
2069 if ((na = numaCreate(bw)) == NULL)
2070 return (NUMA *)ERROR_PTR("na not made", __func__, NULL);
2071 numaSetParameters(na, xstart, 1);
2072 data = pixGetData(pix);
2073 wpl = pixGetWpl(pix);
2074 for (j = xstart; j < xend; j++) {
2075 count = 0;
2076 for (i = ystart; i < yend; i++) {
2077 line = data + i * wpl;
2078 if (GET_DATA_BIT(line, j))
2079 count++;
2080 }
2081 numaAddNumber(na, count);
2082 }
2083
2084 return na;
2085 }
2086
2087
2088 /*!
2089 * \brief pixCountPixelsByRow()
2090 *
2091 * \param[in] pix 1 bpp
2092 * \param[in] tab8 [optional] 8-bit pixel lookup table
2093 * \return na of counts, or NULL on error
2094 */
2095 NUMA *
2096 pixCountPixelsByRow(PIX *pix,
2097 l_int32 *tab8)
2098 {
2099 l_int32 h, i, count;
2100 l_int32 *tab;
2101 NUMA *na;
2102
2103 if (!pix || pixGetDepth(pix) != 1)
2104 return (NUMA *)ERROR_PTR("pix undefined or not 1 bpp", __func__, NULL);
2105
2106 h = pixGetHeight(pix);
2107 if ((na = numaCreate(h)) == NULL)
2108 return (NUMA *)ERROR_PTR("na not made", __func__, NULL);
2109
2110 tab = (tab8) ? tab8 : makePixelSumTab8();
2111 for (i = 0; i < h; i++) {
2112 pixCountPixelsInRow(pix, i, &count, tab);
2113 numaAddNumber(na, count);
2114 }
2115
2116 if (!tab8) LEPT_FREE(tab);
2117 return na;
2118 }
2119
2120
2121 /*!
2122 * \brief pixCountPixelsByColumn()
2123 *
2124 * \param[in] pix 1 bpp
2125 * \return na of counts in each column, or NULL on error
2126 */
2127 NUMA *
2128 pixCountPixelsByColumn(PIX *pix)
2129 {
2130 l_int32 i, j, w, h, wpl;
2131 l_uint32 *line, *data;
2132 l_float32 *array;
2133 NUMA *na;
2134
2135 if (!pix || pixGetDepth(pix) != 1)
2136 return (NUMA *)ERROR_PTR("pix undefined or not 1 bpp", __func__, NULL);
2137
2138 pixGetDimensions(pix, &w, &h, NULL);
2139 if ((na = numaCreate(w)) == NULL)
2140 return (NUMA *)ERROR_PTR("na not made", __func__, NULL);
2141 numaSetCount(na, w);
2142 array = numaGetFArray(na, L_NOCOPY);
2143 data = pixGetData(pix);
2144 wpl = pixGetWpl(pix);
2145 for (i = 0; i < h; i++) {
2146 line = data + wpl * i;
2147 for (j = 0; j < w; j++) {
2148 if (GET_DATA_BIT(line, j))
2149 array[j] += 1.0;
2150 }
2151 }
2152
2153 return na;
2154 }
2155
2156
2157 /*!
2158 * \brief pixCountPixelsInRow()
2159 *
2160 * \param[in] pix 1 bpp
2161 * \param[in] row number
2162 * \param[out] pcount sum of ON pixels in raster line
2163 * \param[in] tab8 [optional] 8-bit pixel lookup table
2164 * \return 0 if OK; 1 on error
2165 */
2166 l_ok
2167 pixCountPixelsInRow(PIX *pix,
2168 l_int32 row,
2169 l_int32 *pcount,
2170 l_int32 *tab8)
2171 {
2172 l_uint32 word, endmask;
2173 l_int32 j, w, h, wpl;
2174 l_int32 fullwords, endbits, sum;
2175 l_int32 *tab;
2176 l_uint32 *line;
2177
2178 if (!pcount)
2179 return ERROR_INT("&count not defined", __func__, 1);
2180 *pcount = 0;
2181 if (!pix || pixGetDepth(pix) != 1)
2182 return ERROR_INT("pix not defined or not 1 bpp", __func__, 1);
2183
2184 pixGetDimensions(pix, &w, &h, NULL);
2185 if (row < 0 || row >= h)
2186 return ERROR_INT("row out of bounds", __func__, 1);
2187 wpl = pixGetWpl(pix);
2188 line = pixGetData(pix) + row * wpl;
2189 fullwords = w >> 5;
2190 endbits = w & 31;
2191 endmask = (endbits == 0) ? 0 : (0xffffffffU << (32 - endbits));
2192
2193 tab = (tab8) ? tab8 : makePixelSumTab8();
2194 sum = 0;
2195 for (j = 0; j < fullwords; j++) {
2196 word = line[j];
2197 if (word) {
2198 sum += tab[word & 0xff] +
2199 tab[(word >> 8) & 0xff] +
2200 tab[(word >> 16) & 0xff] +
2201 tab[(word >> 24) & 0xff];
2202 }
2203 }
2204 if (endbits) {
2205 word = line[j] & endmask;
2206 if (word) {
2207 sum += tab[word & 0xff] +
2208 tab[(word >> 8) & 0xff] +
2209 tab[(word >> 16) & 0xff] +
2210 tab[(word >> 24) & 0xff];
2211 }
2212 }
2213 *pcount = sum;
2214
2215 if (!tab8) LEPT_FREE(tab);
2216 return 0;
2217 }
2218
2219
2220 /*!
2221 * \brief pixGetMomentByColumn()
2222 *
2223 * \param[in] pix 1 bpp
2224 * \param[in] order of moment, either 1 or 2
2225 * \return na of first moment of fg pixels, by column, or NULL on error
2226 */
2227 NUMA *
2228 pixGetMomentByColumn(PIX *pix,
2229 l_int32 order)
2230 {
2231 l_int32 i, j, w, h, wpl;
2232 l_uint32 *line, *data;
2233 l_float32 *array;
2234 NUMA *na;
2235
2236 if (!pix || pixGetDepth(pix) != 1)
2237 return (NUMA *)ERROR_PTR("pix undefined or not 1 bpp", __func__, NULL);
2238 if (order != 1 && order != 2)
2239 return (NUMA *)ERROR_PTR("order of moment not 1 or 2", __func__, NULL);
2240
2241 pixGetDimensions(pix, &w, &h, NULL);
2242 if ((na = numaCreate(w)) == NULL)
2243 return (NUMA *)ERROR_PTR("na not made", __func__, NULL);
2244 numaSetCount(na, w);
2245 array = numaGetFArray(na, L_NOCOPY);
2246 data = pixGetData(pix);
2247 wpl = pixGetWpl(pix);
2248 for (i = 0; i < h; i++) {
2249 line = data + wpl * i;
2250 for (j = 0; j < w; j++) {
2251 if (GET_DATA_BIT(line, j)) {
2252 if (order == 1)
2253 array[j] += i;
2254 else /* order == 2 */
2255 array[j] += i * i;
2256 }
2257 }
2258 }
2259
2260 return na;
2261 }
2262
2263
2264 /*!
2265 * \brief pixThresholdPixelSum()
2266 *
2267 * \param[in] pix 1 bpp
2268 * \param[in] thresh threshold
2269 * \param[out] pabove 1 if above threshold;
2270 * 0 if equal to or less than threshold
2271 * \param[in] tab8 [optional] 8-bit pixel lookup table
2272 * \return 0 if OK; 1 on error
2273 *
2274 * <pre>
2275 * Notes:
2276 * (1) This sums the ON pixels and returns immediately if the count
2277 * goes above threshold. It is therefore more efficient
2278 * for matching images (by running this function on the xor of
2279 * the 2 images) than using pixCountPixels(), which counts all
2280 * pixels before returning.
2281 * </pre>
2282 */
2283 l_ok
2284 pixThresholdPixelSum(PIX *pix,
2285 l_int32 thresh,
2286 l_int32 *pabove,
2287 l_int32 *tab8)
2288 {
2289 l_uint32 word, endmask;
2290 l_int32 *tab;
2291 l_int32 w, h, wpl, i, j;
2292 l_int32 fullwords, endbits, sum;
2293 l_uint32 *line, *data;
2294
2295 if (!pabove)
2296 return ERROR_INT("&above not defined", __func__, 1);
2297 *pabove = 0;
2298 if (!pix || pixGetDepth(pix) != 1)
2299 return ERROR_INT("pix not defined or not 1 bpp", __func__, 1);
2300
2301 tab = (tab8) ? tab8 : makePixelSumTab8();
2302 pixGetDimensions(pix, &w, &h, NULL);
2303 wpl = pixGetWpl(pix);
2304 data = pixGetData(pix);
2305 fullwords = w >> 5;
2306 endbits = w & 31;
2307 endmask = 0xffffffff << (32 - endbits);
2308
2309 sum = 0;
2310 for (i = 0; i < h; i++) {
2311 line = data + wpl * i;
2312 for (j = 0; j < fullwords; j++) {
2313 word = line[j];
2314 if (word) {
2315 sum += tab[word & 0xff] +
2316 tab[(word >> 8) & 0xff] +
2317 tab[(word >> 16) & 0xff] +
2318 tab[(word >> 24) & 0xff];
2319 }
2320 }
2321 if (endbits) {
2322 word = line[j] & endmask;
2323 if (word) {
2324 sum += tab[word & 0xff] +
2325 tab[(word >> 8) & 0xff] +
2326 tab[(word >> 16) & 0xff] +
2327 tab[(word >> 24) & 0xff];
2328 }
2329 }
2330 if (sum > thresh) {
2331 *pabove = 1;
2332 if (!tab8) LEPT_FREE(tab);
2333 return 0;
2334 }
2335 }
2336
2337 if (!tab8) LEPT_FREE(tab);
2338 return 0;
2339 }
2340
2341
2342 /*!
2343 * \brief makePixelSumTab8()
2344 *
2345 * \return table of 256 l_int32.
2346 *
2347 * <pre>
2348 * Notes:
2349 * (1) This table of integers gives the number of 1 bits
2350 * in the 8 bit index.
2351 * </pre>
2352 */
2353 l_int32 *
2354 makePixelSumTab8(void)
2355 {
2356 l_uint8 byte;
2357 l_int32 i;
2358 l_int32 *tab;
2359
2360 tab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
2361 for (i = 0; i < 256; i++) {
2362 byte = (l_uint8)i;
2363 tab[i] = (byte & 0x1) +
2364 ((byte >> 1) & 0x1) +
2365 ((byte >> 2) & 0x1) +
2366 ((byte >> 3) & 0x1) +
2367 ((byte >> 4) & 0x1) +
2368 ((byte >> 5) & 0x1) +
2369 ((byte >> 6) & 0x1) +
2370 ((byte >> 7) & 0x1);
2371 }
2372 return tab;
2373 }
2374
2375
2376 /*!
2377 * \brief makePixelCentroidTab8()
2378 *
2379 * \return table of 256 l_int32.
2380 *
2381 * <pre>
2382 * Notes:
2383 * (1) This table of integers gives the centroid weight of the 1 bits
2384 * in the 8 bit index. In other words, if sumtab is obtained by
2385 * makePixelSumTab8, and centroidtab is obtained by
2386 * makePixelCentroidTab8, then, for 1 <= i <= 255,
2387 * centroidtab[i] / (float)sumtab[i]
2388 * is the centroid of the 1 bits in the 8-bit index i, where the
2389 * MSB is considered to have position 0 and the LSB is considered
2390 * to have position 7.
2391 * </pre>
2392 */
2393 l_int32 *
2394 makePixelCentroidTab8(void)
2395 {
2396 l_int32 i;
2397 l_int32 *tab;
2398
2399 tab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
2400 tab[0] = 0;
2401 tab[1] = 7;
2402 for (i = 2; i < 4; i++) {
2403 tab[i] = tab[i - 2] + 6;
2404 }
2405 for (i = 4; i < 8; i++) {
2406 tab[i] = tab[i - 4] + 5;
2407 }
2408 for (i = 8; i < 16; i++) {
2409 tab[i] = tab[i - 8] + 4;
2410 }
2411 for (i = 16; i < 32; i++) {
2412 tab[i] = tab[i - 16] + 3;
2413 }
2414 for (i = 32; i < 64; i++) {
2415 tab[i] = tab[i - 32] + 2;
2416 }
2417 for (i = 64; i < 128; i++) {
2418 tab[i] = tab[i - 64] + 1;
2419 }
2420 for (i = 128; i < 256; i++) {
2421 tab[i] = tab[i - 128];
2422 }
2423 return tab;
2424 }
2425
2426
2427 /*-------------------------------------------------------------*
2428 * Average of pixel values in gray images *
2429 *-------------------------------------------------------------*/
2430 /*!
2431 * \brief pixAverageByRow()
2432 *
2433 * \param[in] pix 8 or 16 bpp; no colormap
2434 * \param[in] box [optional] clipping box for sum; can be null
2435 * \param[in] type L_WHITE_IS_MAX, L_BLACK_IS_MAX
2436 * \return na of pixel averages by row, or NULL on error
2437 *
2438 * <pre>
2439 * Notes:
2440 * (1) To resample for a bin size different from 1, use
2441 * numaUniformSampling() on the result of this function.
2442 * (2) If type == L_BLACK_IS_MAX, black pixels get the maximum
2443 * value (0xff for 8 bpp, 0xffff for 16 bpp) and white get 0.
2444 * </pre>
2445 */
2446 NUMA *
2447 pixAverageByRow(PIX *pix,
2448 BOX *box,
2449 l_int32 type)
2450 {
2451 l_int32 i, j, w, h, d, wpl, xstart, xend, ystart, yend, bw, bh;
2452 l_uint32 *line, *data;
2453 l_float64 norm, sum;
2454 NUMA *na;
2455
2456 if (!pix)
2457 return (NUMA *)ERROR_PTR("pix not defined", __func__, NULL);
2458 pixGetDimensions(pix, &w, &h, &d);
2459 if (d != 8 && d != 16)
2460 return (NUMA *)ERROR_PTR("pix not 8 or 16 bpp", __func__, NULL);
2461 if (type != L_WHITE_IS_MAX && type != L_BLACK_IS_MAX)
2462 return (NUMA *)ERROR_PTR("invalid type", __func__, NULL);
2463 if (pixGetColormap(pix) != NULL)
2464 return (NUMA *)ERROR_PTR("pix colormapped", __func__, NULL);
2465
2466 if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
2467 &bw, &bh) == 1)
2468 return (NUMA *)ERROR_PTR("invalid clipping box", __func__, NULL);
2469
2470 norm = 1. / (l_float32)bw;
2471 if ((na = numaCreate(bh)) == NULL)
2472 return (NUMA *)ERROR_PTR("na not made", __func__, NULL);
2473 numaSetParameters(na, ystart, 1);
2474 data = pixGetData(pix);
2475 wpl = pixGetWpl(pix);
2476 for (i = ystart; i < yend; i++) {
2477 sum = 0.0;
2478 line = data + i * wpl;
2479 if (d == 8) {
2480 for (j = xstart; j < xend; j++)
2481 sum += GET_DATA_BYTE(line, j);
2482 if (type == L_BLACK_IS_MAX)
2483 sum = bw * 255 - sum;
2484 } else { /* d == 16 */
2485 for (j = xstart; j < xend; j++)
2486 sum += GET_DATA_TWO_BYTES(line, j);
2487 if (type == L_BLACK_IS_MAX)
2488 sum = bw * 0xffff - sum;
2489 }
2490 numaAddNumber(na, (l_float32)(norm * sum));
2491 }
2492
2493 return na;
2494 }
2495
2496
2497 /*!
2498 * \brief pixAverageByColumn()
2499 *
2500 * \param[in] pix 8 or 16 bpp; no colormap
2501 * \param[in] box [optional] clipping box for sum; can be null
2502 * \param[in] type L_WHITE_IS_MAX, L_BLACK_IS_MAX
2503 * \return na of pixel averages by column, or NULL on error
2504 *
2505 * <pre>
2506 * Notes:
2507 * (1) To resample for a bin size different from 1, use
2508 * numaUniformSampling() on the result of this function.
2509 * (2) If type == L_BLACK_IS_MAX, black pixels get the maximum
2510 * value (0xff for 8 bpp, 0xffff for 16 bpp) and white get 0.
2511 * </pre>
2512 */
2513 NUMA *
2514 pixAverageByColumn(PIX *pix,
2515 BOX *box,
2516 l_int32 type)
2517 {
2518 l_int32 i, j, w, h, d, wpl, xstart, xend, ystart, yend, bw, bh;
2519 l_uint32 *line, *data;
2520 l_float32 norm, sum;
2521 NUMA *na;
2522
2523 if (!pix)
2524 return (NUMA *)ERROR_PTR("pix not defined", __func__, NULL);
2525 pixGetDimensions(pix, &w, &h, &d);
2526
2527 if (d != 8 && d != 16)
2528 return (NUMA *)ERROR_PTR("pix not 8 or 16 bpp", __func__, NULL);
2529 if (type != L_WHITE_IS_MAX && type != L_BLACK_IS_MAX)
2530 return (NUMA *)ERROR_PTR("invalid type", __func__, NULL);
2531 if (pixGetColormap(pix) != NULL)
2532 return (NUMA *)ERROR_PTR("pix colormapped", __func__, NULL);
2533
2534 if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
2535 &bw, &bh) == 1)
2536 return (NUMA *)ERROR_PTR("invalid clipping box", __func__, NULL);
2537
2538 if ((na = numaCreate(bw)) == NULL)
2539 return (NUMA *)ERROR_PTR("na not made", __func__, NULL);
2540 numaSetParameters(na, xstart, 1);
2541 norm = 1.f / (l_float32)bh;
2542 data = pixGetData(pix);
2543 wpl = pixGetWpl(pix);
2544 for (j = xstart; j < xend; j++) {
2545 sum = 0.0;
2546 if (d == 8) {
2547 for (i = ystart; i < yend; i++) {
2548 line = data + i * wpl;
2549 sum += GET_DATA_BYTE(line, j);
2550 }
2551 if (type == L_BLACK_IS_MAX)
2552 sum = bh * 255 - sum;
2553 } else { /* d == 16 */
2554 for (i = ystart; i < yend; i++) {
2555 line = data + i * wpl;
2556 sum += GET_DATA_TWO_BYTES(line, j);
2557 }
2558 if (type == L_BLACK_IS_MAX)
2559 sum = bh * 0xffff - sum;
2560 }
2561 numaAddNumber(na, (l_float32)(norm * sum));
2562 }
2563
2564 return na;
2565 }
2566
2567
2568 /*!
2569 * \brief pixAverageInRect()
2570 *
2571 * \param[in] pixs 1, 2, 4, 8 bpp; not cmapped
2572 * \param[in] pixm [optional] 1 bpp mask; if null, use all pixels
2573 * \param[in] box [optional] if null, use entire image
2574 * \param[in] minval ignore values less than this
2575 * \param[in] maxval ignore values greater than this
2576 * \param[in] subsamp subsample factor: integer; use 1 for all pixels
2577 * \param[out] pave average of pixel values under consideration
2578 * \return 0 if OK; 1 on error; 2 if all pixels are filtered out
2579 *
2580 * <pre>
2581 * Notes:
2582 * (1) The average is computed with 4 optional filters: a rectangle,
2583 * a mask, a contiguous set of range values, and subsampling.
2584 * In practice you might use only one or two of these.
2585 * (2) The mask %pixm is a blocking mask: only count pixels in the bg.
2586 * If it exists, alignment is assumed at UL corner and computation
2587 * is over the minimum intersection of %pixs and %pixm.
2588 * If you want the average of pixels under the mask fg, invert it.
2589 * (3) Set the range limits %minval = 0 and %maxval = 255 to use
2590 * all non-masked pixels (regardless of value) in the average.
2591 * (4) If no pixels are used in the averaging, the returned average
2592 * value is 0 and the function returns 2. This is not an error,
2593 * but it says to disregard the returned average value.
2594 * (5) For example, to average all pixels in a given clipping rect %box,
2595 * pixAverageInRect(pixs, NULL, box, 0, 255, 1, &aveval);
2596 * </pre>
2597 */
2598 l_ok
2599 pixAverageInRect(PIX *pixs,
2600 PIX *pixm,
2601 BOX *box,
2602 l_int32 minval,
2603 l_int32 maxval,
2604 l_int32 subsamp,
2605 l_float32 *pave)
2606 {
2607 l_int32 w, h, d, wpls, wm, hm, dm, wplm, val, count;
2608 l_int32 i, j, xstart, xend, ystart, yend;
2609 l_uint32 *datas, *datam = NULL, *lines, *linem = NULL;
2610 l_float64 sum;
2611
2612 if (!pave)
2613 return ERROR_INT("&ave not defined", __func__, 1);
2614 *pave = 0;
2615 if (!pixs)
2616 return ERROR_INT("pixs not defined", __func__, 1);
2617 if (pixGetColormap(pixs) != NULL)
2618 return ERROR_INT("pixs is colormapped", __func__, 1);
2619 pixGetDimensions(pixs, &w, &h, &d);
2620 if (d != 1 && d != 2 && d != 4 && d != 8)
2621 return ERROR_INT("pixs not 1, 2, 4 or 8 bpp", __func__, 1);
2622 if (pixm) {
2623 pixGetDimensions(pixm, &wm, &hm, &dm);
2624 if (dm != 1)
2625 return ERROR_INT("pixm not 1 bpp", __func__, 1);
2626 w = L_MIN(w, wm);
2627 h = L_MIN(h, hm);
2628 }
2629 if (subsamp < 1)
2630 return ERROR_INT("subsamp must be >= 1", __func__, 1);
2631
2632 if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
2633 NULL, NULL) == 1)
2634 return ERROR_INT("invalid clipping box", __func__, 1);
2635
2636 datas = pixGetData(pixs);
2637 wpls = pixGetWpl(pixs);
2638 if (pixm) {
2639 datam = pixGetData(pixm);
2640 wplm = pixGetWpl(pixm);
2641 }
2642 sum = 0.0;
2643 count = 0;
2644 for (i = ystart; i < yend; i += subsamp) {
2645 lines = datas + i * wpls;
2646 if (pixm)
2647 linem = datam + i * wplm;
2648 for (j = xstart; j < xend; j += subsamp) {
2649 if (pixm && (GET_DATA_BIT(linem, j) == 1))
2650 continue;
2651 if (d == 1)
2652 val = GET_DATA_BIT(lines, j);
2653 else if (d == 2)
2654 val = GET_DATA_DIBIT(lines, j);
2655 else if (d == 4)
2656 val = GET_DATA_QBIT(lines, j);
2657 else /* d == 8 */
2658 val = GET_DATA_BYTE(lines, j);
2659 if (val >= minval && val <= maxval) {
2660 sum += val;
2661 count++;
2662 }
2663 }
2664 }
2665
2666 if (count == 0)
2667 return 2; /* not an error; don't use the average value (0.0) */
2668 *pave = sum / (l_float32)count;
2669 return 0;
2670 }
2671
2672
2673 /*-------------------------------------------------------------*
2674 * Average of pixel values in RGB images *
2675 *-------------------------------------------------------------*/
2676 /*!
2677 * \brief pixAverageInRectRGB()
2678 *
2679 * \param[in] pixs rgb; not cmapped
2680 * \param[in] pixm [optional] 1 bpp mask; if null, use all pixels
2681 * \param[in] box [optional] if null, use entire image
2682 * \param[in] subsamp subsample factor: integer; use 1 for all pixels
2683 * \param[out] pave average color of pixel values under consideration,
2684 * in format 0xrrggbb00.
2685 * \return 0 if OK; 1 on error; 2 if all pixels are filtered out
2686 *
2687 * <pre>
2688 * Notes:
2689 * (1) The average is computed with 3 optional filters: a rectangle,
2690 * a mask, and subsampling.
2691 * In practice you might use only one or two of these.
2692 * (2) The mask %pixm is a blocking mask: only count pixels in the bg.
2693 * If it exists, alignment is assumed at UL corner and computation
2694 * is over the minimum intersection of %pixs and %pixm.
2695 * If you want the average of pixels under the mask fg, invert it.
2696 * (3) If no pixels are used in the averaging, the returned average
2697 * value is 0 and the function returns 2. This is not an error,
2698 * but it says to disregard the returned average value.
2699 * (4) For example, to average all pixels in a given clipping rect %box,
2700 * pixAverageInRectRGB(pixs, NULL, box, 1, &aveval);
2701 * </pre>
2702 */
2703 l_ok
2704 pixAverageInRectRGB(PIX *pixs,
2705 PIX *pixm,
2706 BOX *box,
2707 l_int32 subsamp,
2708 l_uint32 *pave)
2709 {
2710 l_int32 w, h, wpls, wm, hm, dm, wplm, i, j, xstart, xend, ystart, yend;
2711 l_int32 rval, gval, bval, rave, gave, bave, count;
2712 l_uint32 *datas, *datam = NULL, *lines, *linem = NULL;
2713 l_uint32 pixel;
2714 l_float64 rsum, gsum, bsum;
2715
2716 if (!pave)
2717 return ERROR_INT("&ave not defined", __func__, 1);
2718 *pave = 0;
2719 if (!pixs || pixGetDepth(pixs) != 32)
2720 return ERROR_INT("pixs undefined or not 32 bpp", __func__, 1);
2721 pixGetDimensions(pixs, &w, &h, NULL);
2722 if (pixm) {
2723 pixGetDimensions(pixm, &wm, &hm, &dm);
2724 if (dm != 1)
2725 return ERROR_INT("pixm not 1 bpp", __func__, 1);
2726 w = L_MIN(w, wm);
2727 h = L_MIN(h, hm);
2728 }
2729 if (subsamp < 1)
2730 return ERROR_INT("subsamp must be >= 1", __func__, 1);
2731
2732 if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
2733 NULL, NULL) == 1)
2734 return ERROR_INT("invalid clipping box", __func__, 1);
2735
2736 datas = pixGetData(pixs);
2737 wpls = pixGetWpl(pixs);
2738 if (pixm) {
2739 datam = pixGetData(pixm);
2740 wplm = pixGetWpl(pixm);
2741 }
2742 rsum = gsum = bsum = 0.0;
2743 count = 0;
2744 for (i = ystart; i < yend; i += subsamp) {
2745 lines = datas + i * wpls;
2746 if (pixm)
2747 linem = datam + i * wplm;
2748 for (j = xstart; j < xend; j += subsamp) {
2749 if (pixm && (GET_DATA_BIT(linem, j) == 1))
2750 continue;
2751 pixel = *(lines + j);
2752 extractRGBValues(pixel, &rval, &gval, &bval);
2753 rsum += rval;
2754 gsum += gval;
2755 bsum += bval;
2756 count++;
2757 }
2758 }
2759
2760 if (count == 0)
2761 return 2; /* not an error */
2762 rave = (l_uint32)(rsum / (l_float64)count);
2763 gave = (l_uint32)(gsum / (l_float64)count);
2764 bave = (l_uint32)(bsum / (l_float64)count);
2765 composeRGBPixel(rave, gave, bave, pave);
2766 return 0;
2767 }
2768
2769
2770 /*------------------------------------------------------------------*
2771 * Variance of pixel values in gray images *
2772 *------------------------------------------------------------------*/
2773 /*!
2774 * \brief pixVarianceByRow()
2775 *
2776 * \param[in] pix 8 or 16 bpp; no colormap
2777 * \param[in] box [optional] clipping box for variance; can be null
2778 * \return na of rmsdev by row, or NULL on error
2779 *
2780 * <pre>
2781 * Notes:
2782 * (1) To resample for a bin size different from 1, use
2783 * numaUniformSampling() on the result of this function.
2784 * (2) We are actually computing the RMS deviation in each row.
2785 * This is the square root of the variance.
2786 * </pre>
2787 */
2788 NUMA *
2789 pixVarianceByRow(PIX *pix,
2790 BOX *box)
2791 {
2792 l_int32 i, j, w, h, d, wpl, xstart, xend, ystart, yend, bw, bh, val;
2793 l_uint32 *line, *data;
2794 l_float64 sum1, sum2, norm, ave, var, rootvar;
2795 NUMA *na;
2796
2797 if (!pix)
2798 return (NUMA *)ERROR_PTR("pix not defined", __func__, NULL);
2799 pixGetDimensions(pix, &w, &h, &d);
2800 if (d != 8 && d != 16)
2801 return (NUMA *)ERROR_PTR("pix not 8 or 16 bpp", __func__, NULL);
2802 if (pixGetColormap(pix) != NULL)
2803 return (NUMA *)ERROR_PTR("pix colormapped", __func__, NULL);
2804
2805 if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
2806 &bw, &bh) == 1)
2807 return (NUMA *)ERROR_PTR("invalid clipping box", __func__, NULL);
2808
2809 if ((na = numaCreate(bh)) == NULL)
2810 return (NUMA *)ERROR_PTR("na not made", __func__, NULL);
2811 numaSetParameters(na, ystart, 1);
2812 norm = 1. / (l_float32)bw;
2813 data = pixGetData(pix);
2814 wpl = pixGetWpl(pix);
2815 for (i = ystart; i < yend; i++) {
2816 sum1 = sum2 = 0.0;
2817 line = data + i * wpl;
2818 for (j = xstart; j < xend; j++) {
2819 if (d == 8)
2820 val = GET_DATA_BYTE(line, j);
2821 else /* d == 16 */
2822 val = GET_DATA_TWO_BYTES(line, j);
2823 sum1 += val;
2824 sum2 += (l_float64)(val) * val;
2825 }
2826 ave = norm * sum1;
2827 var = norm * sum2 - ave * ave;
2828 rootvar = sqrt(var);
2829 numaAddNumber(na, (l_float32)rootvar);
2830 }
2831
2832 return na;
2833 }
2834
2835
2836 /*!
2837 * \brief pixVarianceByColumn()
2838 *
2839 * \param[in] pix 8 or 16 bpp; no colormap
2840 * \param[in] box [optional] clipping box for variance; can be null
2841 * \return na of rmsdev by column, or NULL on error
2842 *
2843 * <pre>
2844 * Notes:
2845 * (1) To resample for a bin size different from 1, use
2846 * numaUniformSampling() on the result of this function.
2847 * (2) We are actually computing the RMS deviation in each row.
2848 * This is the square root of the variance.
2849 * </pre>
2850 */
2851 NUMA *
2852 pixVarianceByColumn(PIX *pix,
2853 BOX *box)
2854 {
2855 l_int32 i, j, w, h, d, wpl, xstart, xend, ystart, yend, bw, bh, val;
2856 l_uint32 *line, *data;
2857 l_float64 sum1, sum2, norm, ave, var, rootvar;
2858 NUMA *na;
2859
2860 if (!pix)
2861 return (NUMA *)ERROR_PTR("pix not defined", __func__, NULL);
2862 pixGetDimensions(pix, &w, &h, &d);
2863 if (d != 8 && d != 16)
2864 return (NUMA *)ERROR_PTR("pix not 8 or 16 bpp", __func__, NULL);
2865 if (pixGetColormap(pix) != NULL)
2866 return (NUMA *)ERROR_PTR("pix colormapped", __func__, NULL);
2867
2868 if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
2869 &bw, &bh) == 1)
2870 return (NUMA *)ERROR_PTR("invalid clipping box", __func__, NULL);
2871
2872 if ((na = numaCreate(bw)) == NULL)
2873 return (NUMA *)ERROR_PTR("na not made", __func__, NULL);
2874 numaSetParameters(na, xstart, 1);
2875 norm = 1. / (l_float32)bh;
2876 data = pixGetData(pix);
2877 wpl = pixGetWpl(pix);
2878 for (j = xstart; j < xend; j++) {
2879 sum1 = sum2 = 0.0;
2880 for (i = ystart; i < yend; i++) {
2881 line = data + wpl * i;
2882 if (d == 8)
2883 val = GET_DATA_BYTE(line, j);
2884 else /* d == 16 */
2885 val = GET_DATA_TWO_BYTES(line, j);
2886 sum1 += val;
2887 sum2 += (l_float64)(val) * val;
2888 }
2889 ave = norm * sum1;
2890 var = norm * sum2 - ave * ave;
2891 rootvar = sqrt(var);
2892 numaAddNumber(na, (l_float32)rootvar);
2893 }
2894
2895 return na;
2896 }
2897
2898
2899 /*!
2900 * \brief pixVarianceInRect()
2901 *
2902 * \param[in] pix 1, 2, 4, 8 bpp; not cmapped
2903 * \param[in] box [optional] if null, use entire image
2904 * \param[out] prootvar sqrt variance of pixel values in region
2905 * \return 0 if OK; 1 on error
2906 */
2907 l_ok
2908 pixVarianceInRect(PIX *pix,
2909 BOX *box,
2910 l_float32 *prootvar)
2911 {
2912 l_int32 w, h, d, wpl, i, j, xstart, xend, ystart, yend, bw, bh, val;
2913 l_uint32 *data, *line;
2914 l_float64 sum1, sum2, norm, ave, var;
2915
2916 if (!prootvar)
2917 return ERROR_INT("&rootvar not defined", __func__, 1);
2918 *prootvar = 0.0;
2919 if (!pix)
2920 return ERROR_INT("pix not defined", __func__, 1);
2921 pixGetDimensions(pix, &w, &h, &d);
2922 if (d != 1 && d != 2 && d != 4 && d != 8)
2923 return ERROR_INT("pix not 1, 2, 4 or 8 bpp", __func__, 1);
2924 if (pixGetColormap(pix) != NULL)
2925 return ERROR_INT("pix is colormapped", __func__, 1);
2926
2927 if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
2928 &bw, &bh) == 1)
2929 return ERROR_INT("invalid clipping box", __func__, 1);
2930
2931 wpl = pixGetWpl(pix);
2932 data = pixGetData(pix);
2933 sum1 = sum2 = 0.0;
2934 for (i = ystart; i < yend; i++) {
2935 line = data + i * wpl;
2936 for (j = xstart; j < xend; j++) {
2937 if (d == 1) {
2938 val = GET_DATA_BIT(line, j);
2939 sum1 += val;
2940 sum2 += (l_float64)(val) * val;
2941 } else if (d == 2) {
2942 val = GET_DATA_DIBIT(line, j);
2943 sum1 += val;
2944 sum2 += (l_float64)(val) * val;
2945 } else if (d == 4) {
2946 val = GET_DATA_QBIT(line, j);
2947 sum1 += val;
2948 sum2 += (l_float64)(val) * val;
2949 } else { /* d == 8 */
2950 val = GET_DATA_BYTE(line, j);
2951 sum1 += val;
2952 sum2 += (l_float64)(val) * val;
2953 }
2954 }
2955 }
2956 norm = 1.0 / ((l_float64)(bw) * bh);
2957 ave = norm * sum1;
2958 var = norm * sum2 - ave * ave;
2959 *prootvar = (l_float32)sqrt(var);
2960 return 0;
2961 }
2962
2963
2964 /*---------------------------------------------------------------------*
2965 * Average of absolute value of pixel differences in gray images *
2966 *---------------------------------------------------------------------*/
2967 /*!
2968 * \brief pixAbsDiffByRow()
2969 *
2970 * \param[in] pix 8 bpp; no colormap
2971 * \param[in] box [optional] clipping box for region; can be null
2972 * \return na of abs val pixel difference averages by row, or NULL on error
2973 *
2974 * <pre>
2975 * Notes:
2976 * (1) This is an average over differences of adjacent pixels along
2977 * each row.
2978 * (2) To resample for a bin size different from 1, use
2979 * numaUniformSampling() on the result of this function.
2980 * </pre>
2981 */
2982 NUMA *
2983 pixAbsDiffByRow(PIX *pix,
2984 BOX *box)
2985 {
2986 l_int32 i, j, w, h, wpl, xstart, xend, ystart, yend, bw, bh, val0, val1;
2987 l_uint32 *line, *data;
2988 l_float64 norm, sum;
2989 NUMA *na;
2990
2991 if (!pix || pixGetDepth(pix) != 8)
2992 return (NUMA *)ERROR_PTR("pix undefined or not 8 bpp", __func__, NULL);
2993 if (pixGetColormap(pix) != NULL)
2994 return (NUMA *)ERROR_PTR("pix colormapped", __func__, NULL);
2995
2996 pixGetDimensions(pix, &w, &h, NULL);
2997 if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
2998 &bw, &bh) == 1)
2999 return (NUMA *)ERROR_PTR("invalid clipping box", __func__, NULL);
3000 if (bw < 2)
3001 return (NUMA *)ERROR_PTR("row width must be >= 2", __func__, NULL);
3002
3003 norm = 1. / (l_float32)(bw - 1);
3004 if ((na = numaCreate(bh)) == NULL)
3005 return (NUMA *)ERROR_PTR("na not made", __func__, NULL);
3006 numaSetParameters(na, ystart, 1);
3007 data = pixGetData(pix);
3008 wpl = pixGetWpl(pix);
3009 for (i = ystart; i < yend; i++) {
3010 sum = 0.0;
3011 line = data + i * wpl;
3012 val0 = GET_DATA_BYTE(line, xstart);
3013 for (j = xstart + 1; j < xend; j++) {
3014 val1 = GET_DATA_BYTE(line, j);
3015 sum += L_ABS(val1 - val0);
3016 val0 = val1;
3017 }
3018 numaAddNumber(na, (l_float32)(norm * sum));
3019 }
3020
3021 return na;
3022 }
3023
3024
3025 /*!
3026 * \brief pixAbsDiffByColumn()
3027 *
3028 * \param[in] pix 8 bpp; no colormap
3029 * \param[in] box [optional] clipping box for region; can be null
3030 * \return na of abs val pixel difference averages by column,
3031 * or NULL on error
3032 *
3033 * <pre>
3034 * Notes:
3035 * (1) This is an average over differences of adjacent pixels along
3036 * each column.
3037 * (2) To resample for a bin size different from 1, use
3038 * numaUniformSampling() on the result of this function.
3039 * </pre>
3040 */
3041 NUMA *
3042 pixAbsDiffByColumn(PIX *pix,
3043 BOX *box)
3044 {
3045 l_int32 i, j, w, h, wpl, xstart, xend, ystart, yend, bw, bh, val0, val1;
3046 l_uint32 *line, *data;
3047 l_float64 norm, sum;
3048 NUMA *na;
3049
3050 if (!pix || pixGetDepth(pix) != 8)
3051 return (NUMA *)ERROR_PTR("pix undefined or not 8 bpp", __func__, NULL);
3052 if (pixGetColormap(pix) != NULL)
3053 return (NUMA *)ERROR_PTR("pix colormapped", __func__, NULL);
3054
3055 pixGetDimensions(pix, &w, &h, NULL);
3056 if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
3057 &bw, &bh) == 1)
3058 return (NUMA *)ERROR_PTR("invalid clipping box", __func__, NULL);
3059 if (bh < 2)
3060 return (NUMA *)ERROR_PTR("column height must be >= 2", __func__, NULL);
3061
3062 norm = 1. / (l_float32)(bh - 1);
3063 if ((na = numaCreate(bw)) == NULL)
3064 return (NUMA *)ERROR_PTR("na not made", __func__, NULL);
3065 numaSetParameters(na, xstart, 1);
3066 data = pixGetData(pix);
3067 wpl = pixGetWpl(pix);
3068 for (j = xstart; j < xend; j++) {
3069 sum = 0.0;
3070 line = data + ystart * wpl;
3071 val0 = GET_DATA_BYTE(line, j);
3072 for (i = ystart + 1; i < yend; i++) {
3073 line = data + i * wpl;
3074 val1 = GET_DATA_BYTE(line, j);
3075 sum += L_ABS(val1 - val0);
3076 val0 = val1;
3077 }
3078 numaAddNumber(na, (l_float32)(norm * sum));
3079 }
3080
3081 return na;
3082 }
3083
3084
3085 /*!
3086 * \brief pixAbsDiffInRect()
3087 *
3088 * \param[in] pix 8 bpp; not cmapped
3089 * \param[in] box [optional] if null, use entire image
3090 * \param[in] dir differences along L_HORIZONTAL_LINE or L_VERTICAL_LINE
3091 * \param[out] pabsdiff average of abs diff pixel values in region
3092 * \return 0 if OK; 1 on error
3093 *
3094 * <pre>
3095 * Notes:
3096 * (1) This gives the average over the abs val of differences of
3097 * adjacent pixels values, along either each
3098 * row: dir == L_HORIZONTAL_LINE
3099 * column: dir == L_VERTICAL_LINE
3100 * </pre>
3101 */
3102 l_ok
3103 pixAbsDiffInRect(PIX *pix,
3104 BOX *box,
3105 l_int32 dir,
3106 l_float32 *pabsdiff)
3107 {
3108 l_int32 w, h, wpl, i, j, xstart, xend, ystart, yend, bw, bh, val0, val1;
3109 l_uint32 *data, *line;
3110 l_float64 norm, sum;
3111
3112 if (!pabsdiff)
3113 return ERROR_INT("&absdiff not defined", __func__, 1);
3114 *pabsdiff = 0.0;
3115 if (!pix || pixGetDepth(pix) != 8)
3116 return ERROR_INT("pix undefined or not 8 bpp", __func__, 1);
3117 if (dir != L_HORIZONTAL_LINE && dir != L_VERTICAL_LINE)
3118 return ERROR_INT("invalid direction", __func__, 1);
3119 if (pixGetColormap(pix) != NULL)
3120 return ERROR_INT("pix is colormapped", __func__, 1);
3121
3122 pixGetDimensions(pix, &w, &h, NULL);
3123 if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
3124 &bw, &bh) == 1)
3125 return ERROR_INT("invalid clipping box", __func__, 1);
3126
3127 wpl = pixGetWpl(pix);
3128 data = pixGetData(pix);
3129 if (dir == L_HORIZONTAL_LINE) {
3130 norm = 1. / (l_float32)(bh * (bw - 1));
3131 sum = 0.0;
3132 for (i = ystart; i < yend; i++) {
3133 line = data + i * wpl;
3134 val0 = GET_DATA_BYTE(line, xstart);
3135 for (j = xstart + 1; j < xend; j++) {
3136 val1 = GET_DATA_BYTE(line, j);
3137 sum += L_ABS(val1 - val0);
3138 val0 = val1;
3139 }
3140 }
3141 } else { /* vertical line */
3142 norm = 1. / (l_float32)(bw * (bh - 1));
3143 sum = 0.0;
3144 for (j = xstart; j < xend; j++) {
3145 line = data + ystart * wpl;
3146 val0 = GET_DATA_BYTE(line, j);
3147 for (i = ystart + 1; i < yend; i++) {
3148 line = data + i * wpl;
3149 val1 = GET_DATA_BYTE(line, j);
3150 sum += L_ABS(val1 - val0);
3151 val0 = val1;
3152 }
3153 }
3154 }
3155 *pabsdiff = (l_float32)(norm * sum);
3156 return 0;
3157 }
3158
3159
3160 /*!
3161 * \brief pixAbsDiffOnLine()
3162 *
3163 * \param[in] pix 8 bpp; not cmapped
3164 * \param[in] x1, y1 first point; x1 <= x2, y1 <= y2
3165 * \param[in] x2, y2 first point
3166 * \param[out] pabsdiff average of abs diff pixel values on line
3167 * \return 0 if OK; 1 on error
3168 *
3169 * <pre>
3170 * Notes:
3171 * (1) This gives the average over the abs val of differences of
3172 * adjacent pixels values, along a line that is either horizontal
3173 * or vertical.
3174 * (2) If horizontal, require x1 < x2; if vertical, require y1 < y2.
3175 * </pre>
3176 */
3177 l_ok
3178 pixAbsDiffOnLine(PIX *pix,
3179 l_int32 x1,
3180 l_int32 y1,
3181 l_int32 x2,
3182 l_int32 y2,
3183 l_float32 *pabsdiff)
3184 {
3185 l_int32 w, h, i, j, dir, size, sum;
3186 l_uint32 val0, val1;
3187
3188 if (!pabsdiff)
3189 return ERROR_INT("&absdiff not defined", __func__, 1);
3190 *pabsdiff = 0.0;
3191 if (!pix || pixGetDepth(pix) != 8)
3192 return ERROR_INT("pix undefined or not 8 bpp", __func__, 1);
3193 if (y1 == y2) {
3194 dir = L_HORIZONTAL_LINE;
3195 } else if (x1 == x2) {
3196 dir = L_VERTICAL_LINE;
3197 } else {
3198 return ERROR_INT("line is neither horiz nor vert", __func__, 1);
3199 }
3200 if (pixGetColormap(pix) != NULL)
3201 return ERROR_INT("pix is colormapped", __func__, 1);
3202
3203 pixGetDimensions(pix, &w, &h, NULL);
3204 sum = 0;
3205 if (dir == L_HORIZONTAL_LINE) {
3206 x1 = L_MAX(x1, 0);
3207 x2 = L_MIN(x2, w - 1);
3208 if (x1 >= x2)
3209 return ERROR_INT("x1 >= x2", __func__, 1);
3210 size = x2 - x1;
3211 pixGetPixel(pix, x1, y1, &val0);
3212 for (j = x1 + 1; j <= x2; j++) {
3213 pixGetPixel(pix, j, y1, &val1);
3214 sum += L_ABS((l_int32)val1 - (l_int32)val0);
3215 val0 = val1;
3216 }
3217 } else { /* vertical */
3218 y1 = L_MAX(y1, 0);
3219 y2 = L_MIN(y2, h - 1);
3220 if (y1 >= y2)
3221 return ERROR_INT("y1 >= y2", __func__, 1);
3222 size = y2 - y1;
3223 pixGetPixel(pix, x1, y1, &val0);
3224 for (i = y1 + 1; i <= y2; i++) {
3225 pixGetPixel(pix, x1, i, &val1);
3226 sum += L_ABS((l_int32)val1 - (l_int32)val0);
3227 val0 = val1;
3228 }
3229 }
3230 *pabsdiff = (l_float32)sum / (l_float32)size;
3231 return 0;
3232 }
3233
3234
3235 /*-------------------------------------------------------------*
3236 * Count of pixels with specific value *
3237 *-------------------------------------------------------------*/
3238 /*!
3239 * \brief pixCountArbInRect()
3240 *
3241 * \param[in] pixs 1,2,4,8 bpp; can be colormapped
3242 * \param[in] box [optional] over which count is made;
3243 * use entire image if NULL
3244 * \param[in] val pixel value to count
3245 * \param[in] factor subsampling factor; integer >= 1
3246 * \param[out] pcount count; estimate it if factor > 1
3247 * \return na histogram, or NULL on error
3248 *
3249 * <pre>
3250 * Notes:
3251 * (1) If pixs is cmapped, %val is compared to the colormap index;
3252 * otherwise, %val is compared to the grayscale value.
3253 * (2) Set the subsampling %factor > 1 to reduce the amount of computation.
3254 * If %factor > 1, multiply the count by %factor * %factor.
3255 * </pre>
3256 */
3257 l_int32
3258 pixCountArbInRect(PIX *pixs,
3259 BOX *box,
3260 l_int32 val,
3261 l_int32 factor,
3262 l_int32 *pcount)
3263 {
3264 l_int32 i, j, bx, by, bw, bh, w, h, d, wpl, pixval;
3265 l_uint32 *data, *line;
3266
3267 if (!pcount)
3268 return ERROR_INT("&count not defined", __func__, 1);
3269 *pcount = 0;
3270 if (!pixs)
3271 return ERROR_INT("pixs not defined", __func__, 1);
3272 d = pixGetDepth(pixs);
3273 if (d != 1 && d != 2 && d != 4 && d != 8)
3274 return ERROR_INT("pixs not 1, 2, 4 or 8 bpp", __func__, 1);
3275 if (val < 0)
3276 return ERROR_INT("val < 0", __func__, 1);
3277 if (val > (1 << d) - 1) {
3278 L_ERROR("invalid val = %d for depth %d\n", __func__, val, d);
3279 return 1;
3280 }
3281 if (factor < 1)
3282 return ERROR_INT("sampling factor < 1", __func__, 1);
3283
3284 pixGetDimensions(pixs, &w, &h, NULL);
3285 data = pixGetData(pixs);
3286 wpl = pixGetWpl(pixs);
3287 if (!box) {
3288 for (i = 0; i < h; i += factor) {
3289 line = data + i * wpl;
3290 for (j = 0; j < w; j += factor) {
3291 if (d == 8) {
3292 pixval = GET_DATA_BYTE(line, j);
3293 } else if (d == 1) {
3294 pixval = GET_DATA_BIT(line, j);
3295 } else if (d == 2) {
3296 pixval = GET_DATA_DIBIT(line, j);
3297 } else /* d == 4 */ {
3298 pixval = GET_DATA_QBIT(line, j);
3299 }
3300 if (pixval == val) (*pcount)++;
3301 }
3302 }
3303 } else {
3304 boxGetGeometry(box, &bx, &by, &bw, &bh);
3305 for (i = 0; i < bh; i += factor) {
3306 if (by + i < 0 || by + i >= h) continue;
3307 line = data + (by + i) * wpl;
3308 for (j = 0; j < bw; j += factor) {
3309 if (bx + j < 0 || bx + j >= w) continue;
3310 if (d == 8) {
3311 pixval = GET_DATA_BYTE(line, bx + j);
3312 } else if (d == 1) {
3313 pixval = GET_DATA_BIT(line, bx + j);
3314 } else if (d == 2) {
3315 pixval = GET_DATA_DIBIT(line, bx + j);
3316 } else /* d == 4 */ {
3317 pixval = GET_DATA_QBIT(line, bx + j);
3318 }
3319 if (pixval == val) (*pcount)++;
3320 }
3321 }
3322 }
3323
3324 if (factor > 1) /* assume pixel color is randomly distributed */
3325 *pcount = *pcount * factor * factor;
3326 return 0;
3327 }
3328
3329
3330 /*-------------------------------------------------------------*
3331 * Mirrored tiling of a smaller image *
3332 *-------------------------------------------------------------*/
3333 /*!
3334 * \brief pixMirroredTiling()
3335 *
3336 * \param[in] pixs 8 or 32 bpp, small tile; to be replicated
3337 * \param[in] w, h dimensions of output pix
3338 * \return pixd usually larger pix, mirror-tiled with pixs,
3339 * or NULL on error
3340 *
3341 * <pre>
3342 * Notes:
3343 * (1) This uses mirrored tiling, where each row alternates
3344 * with LR flips and every column alternates with TB
3345 * flips, such that the result is a tiling with identical
3346 * 2 x 2 tiles, each of which is composed of these transforms:
3347 * -----------------
3348 * | 1 | LR |
3349 * -----------------
3350 * | TB | LR/TB |
3351 * -----------------
3352 * </pre>
3353 */
3354 PIX *
3355 pixMirroredTiling(PIX *pixs,
3356 l_int32 w,
3357 l_int32 h)
3358 {
3359 l_int32 wt, ht, d, i, j, nx, ny;
3360 PIX *pixd, *pixsfx, *pixsfy, *pixsfxy, *pix;
3361
3362 if (!pixs)
3363 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
3364 pixGetDimensions(pixs, &wt, &ht, &d);
3365 if (wt <= 0 || ht <= 0)
3366 return (PIX *)ERROR_PTR("pixs size illegal", __func__, NULL);
3367 if (d != 8 && d != 32)
3368 return (PIX *)ERROR_PTR("depth not 32 bpp", __func__, NULL);
3369
3370 if ((pixd = pixCreate(w, h, d)) == NULL)
3371 return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
3372 pixCopySpp(pixd, pixs);
3373
3374 nx = (w + wt - 1) / wt;
3375 ny = (h + ht - 1) / ht;
3376 pixsfx = pixFlipLR(NULL, pixs);
3377 pixsfy = pixFlipTB(NULL, pixs);
3378 pixsfxy = pixFlipTB(NULL, pixsfx);
3379 for (i = 0; i < ny; i++) {
3380 for (j = 0; j < nx; j++) {
3381 pix = pixs;
3382 if ((i & 1) && !(j & 1))
3383 pix = pixsfy;
3384 else if (!(i & 1) && (j & 1))
3385 pix = pixsfx;
3386 else if ((i & 1) && (j & 1))
3387 pix = pixsfxy;
3388 pixRasterop(pixd, j * wt, i * ht, wt, ht, PIX_SRC, pix, 0, 0);
3389 }
3390 }
3391
3392 pixDestroy(&pixsfx);
3393 pixDestroy(&pixsfy);
3394 pixDestroy(&pixsfxy);
3395 return pixd;
3396 }
3397
3398
3399 /*!
3400 * \brief pixFindRepCloseTile()
3401 *
3402 * \param[in] pixs 32 bpp rgb
3403 * \param[in] box region of pixs to search around
3404 * \param[in] searchdir L_HORIZ or L_VERT; direction to search
3405 * \param[in] mindist min distance of selected tile edge from box; >= 0
3406 * \param[in] tsize tile size; > 1; even; typically ~50
3407 * \param[in] ntiles number of tiles tested in each row/column
3408 * \param[out] pboxtile region of best tile
3409 * \param[in] debug 1 for debug output
3410 * \return 0 if OK, 1 on error
3411 *
3412 * <pre>
3413 * Notes:
3414 * (1) This looks for one or two square tiles with conforming median
3415 * intensity and low variance, that is outside but near the input box.
3416 * (2) %mindist specifies the gap between the box and the
3417 * potential tiles. The tiles are given an overlap of 50%.
3418 * %ntiles specifies the number of tiles that are tested
3419 * beyond %mindist for each row or column.
3420 * (3) For example, if %mindist = 20, %tilesize = 50 and %ntiles = 3,
3421 * a horizontal search to the right will have 3 tiles in each row,
3422 * with left edges at 20, 45 and 70 from the right edge of the
3423 * input %box. The number of rows of tiles is determined by
3424 * the height of %box and %tsize, with the 50% overlap..
3425 * </pre>
3426 */
3427 l_ok
3428 pixFindRepCloseTile(PIX *pixs,
3429 BOX *box,
3430 l_int32 searchdir,
3431 l_int32 mindist,
3432 l_int32 tsize,
3433 l_int32 ntiles,
3434 BOX **pboxtile,
3435 l_int32 debug)
3436 {
3437 l_int32 w, h, i, n, bestindex;
3438 l_float32 var_of_mean, median_of_mean, median_of_stdev, mean_val, stdev_val;
3439 l_float32 mindels, bestdelm, delm, dels, mean, stdev;
3440 BOXA *boxa;
3441 NUMA *namean, *nastdev;
3442 PIX *pix, *pixg;
3443 PIXA *pixa;
3444
3445 if (!pboxtile)
3446 return ERROR_INT("&boxtile not defined", __func__, 1);
3447 *pboxtile = NULL;
3448 if (!pixs)
3449 return ERROR_INT("pixs not defined", __func__, 1);
3450 if (!box)
3451 return ERROR_INT("box not defined", __func__, 1);
3452 if (searchdir != L_HORIZ && searchdir != L_VERT)
3453 return ERROR_INT("invalid searchdir", __func__, 1);
3454 if (mindist < 0)
3455 return ERROR_INT("mindist must be >= 0", __func__, 1);
3456 if (tsize < 2)
3457 return ERROR_INT("tsize must be > 1", __func__, 1);
3458 if (ntiles > 7) {
3459 L_WARNING("ntiles = %d; larger than suggested max of 7\n",
3460 __func__, ntiles);
3461 }
3462
3463 /* Locate tile regions */
3464 pixGetDimensions(pixs, &w, &h, NULL);
3465 boxa = findTileRegionsForSearch(box, w, h, searchdir, mindist,
3466 tsize, ntiles);
3467 if (!boxa)
3468 return ERROR_INT("no tiles found", __func__, 1);
3469
3470 /* Generate the tiles and the mean and stdev of intensity */
3471 pixa = pixClipRectangles(pixs, boxa);
3472 n = pixaGetCount(pixa);
3473 namean = numaCreate(n);
3474 nastdev = numaCreate(n);
3475 for (i = 0; i < n; i++) {
3476 pix = pixaGetPix(pixa, i, L_CLONE);
3477 pixg = pixConvertRGBToGray(pix, 0.33f, 0.34f, 0.33f);
3478 pixGetAverageMasked(pixg, NULL, 0, 0, 1, L_MEAN_ABSVAL, &mean);
3479 pixGetAverageMasked(pixg, NULL, 0, 0, 1, L_STANDARD_DEVIATION, &stdev);
3480 numaAddNumber(namean, mean);
3481 numaAddNumber(nastdev, stdev);
3482 pixDestroy(&pix);
3483 pixDestroy(&pixg);
3484 }
3485
3486 /* Find the median and variance of the averages. We require
3487 * the best tile to have a mean pixel intensity within a standard
3488 * deviation of the median of mean intensities, and choose the
3489 * tile in that set with the smallest stdev of pixel intensities
3490 * (as a proxy for the tile with least visible structure).
3491 * The median of the stdev is used, for debugging, as a normalizing
3492 * factor for the stdev of intensities within a tile. */
3493 numaGetStatsUsingHistogram(namean, 256, NULL, NULL, NULL, &var_of_mean,
3494 &median_of_mean, 0.0, NULL, NULL);
3495 numaGetStatsUsingHistogram(nastdev, 256, NULL, NULL, NULL, NULL,
3496 &median_of_stdev, 0.0, NULL, NULL);
3497 mindels = 1000.0;
3498 bestdelm = 1000.0;
3499 bestindex = 0;
3500 for (i = 0; i < n; i++) {
3501 numaGetFValue(namean, i, &mean_val);
3502 numaGetFValue(nastdev, i, &stdev_val);
3503 if (var_of_mean == 0.0) { /* uniform color; any box will do */
3504 delm = 0.0; /* any value < 1.01 */
3505 dels = 1.0; /* n'importe quoi */
3506 } else {
3507 delm = L_ABS(mean_val - median_of_mean) / sqrt(var_of_mean);
3508 dels = stdev_val / median_of_stdev;
3509 }
3510 if (delm < 1.01) {
3511 if (dels < mindels) {
3512 if (debug) {
3513 lept_stderr("i = %d, mean = %7.3f, delm = %7.3f,"
3514 " stdev = %7.3f, dels = %7.3f\n",
3515 i, mean_val, delm, stdev_val, dels);
3516 }
3517 mindels = dels;
3518 bestdelm = delm;
3519 bestindex = i;
3520 }
3521 }
3522 }
3523 *pboxtile = boxaGetBox(boxa, bestindex, L_COPY);
3524
3525 if (debug) {
3526 L_INFO("median of mean = %7.3f\n", __func__, median_of_mean);
3527 L_INFO("standard dev of mean = %7.3f\n", __func__, sqrt(var_of_mean));
3528 L_INFO("median of stdev = %7.3f\n", __func__, median_of_stdev);
3529 L_INFO("best tile: index = %d\n", __func__, bestindex);
3530 L_INFO("delta from median in units of stdev = %5.3f\n",
3531 __func__, bestdelm);
3532 L_INFO("stdev as fraction of median stdev = %5.3f\n",
3533 __func__, mindels);
3534 }
3535
3536 numaDestroy(&namean);
3537 numaDestroy(&nastdev);
3538 pixaDestroy(&pixa);
3539 boxaDestroy(&boxa);
3540 return 0;
3541 }
3542
3543
3544 /*!
3545 * \brief findTileRegionsForSearch()
3546 *
3547 * \param[in] box region of Pix to search around
3548 * \param[in] w, h dimensions of Pix
3549 * \param[in] searchdir L_HORIZ or L_VERT; direction to search
3550 * \param[in] mindist min distance of selected tile edge from box; >= 0
3551 * \param[in] tsize tile size; > 1; even; typically ~50
3552 * \param[in] ntiles number of tiles tested in each row/column
3553 * \return boxa if OK, or NULL on error
3554 *
3555 * <pre>
3556 * Notes:
3557 * (1) See calling function pixfindRepCloseTile().
3558 * </pre>
3559 */
3560 static BOXA *
3561 findTileRegionsForSearch(BOX *box,
3562 l_int32 w,
3563 l_int32 h,
3564 l_int32 searchdir,
3565 l_int32 mindist,
3566 l_int32 tsize,
3567 l_int32 ntiles)
3568 {
3569 l_int32 bx, by, bw, bh, left, right, top, bot, i, j, nrows, ncols;
3570 l_int32 x0, y0, x, y, w_avail, w_needed, h_avail, h_needed, t_avail;
3571 BOX *box1;
3572 BOXA *boxa;
3573
3574 if (!box)
3575 return (BOXA *)ERROR_PTR("box not defined", __func__, NULL);
3576 if (ntiles == 0)
3577 return (BOXA *)ERROR_PTR("no tiles requested", __func__, NULL);
3578
3579 boxGetGeometry(box, &bx, &by, &bw, &bh);
3580 if (searchdir == L_HORIZ) {
3581 /* Find the tile parameters for the search. Note that the
3582 * tiles are overlapping by 50% in each direction. */
3583 left = bx; /* distance to left of box */
3584 right = w - bx - bw + 1; /* distance to right of box */
3585 w_avail = L_MAX(left, right) - mindist;
3586 if (tsize & 1) tsize++; /* be sure it's even */
3587 if (w_avail < tsize) {
3588 L_ERROR("tsize = %d, w_avail = %d\n", __func__, tsize, w_avail);
3589 return NULL;
3590 }
3591 w_needed = tsize + (ntiles - 1) * (tsize / 2);
3592 if (w_needed > w_avail) {
3593 t_avail = 1 + 2 * (w_avail - tsize) / tsize;
3594 L_WARNING("ntiles = %d; room for only %d\n", __func__,
3595 ntiles, t_avail);
3596 ntiles = t_avail;
3597 w_needed = tsize + (ntiles - 1) * (tsize / 2);
3598 }
3599 nrows = L_MAX(1, 1 + 2 * (bh - tsize) / tsize);
3600
3601 /* Generate the tile regions to search */
3602 boxa = boxaCreate(0);
3603 if (left > right) /* search to left */
3604 x0 = bx - w_needed;
3605 else /* search to right */
3606 x0 = bx + bw + mindist;
3607 for (i = 0; i < nrows; i++) {
3608 y = by + i * tsize / 2;
3609 for (j = 0; j < ntiles; j++) {
3610 x = x0 + j * tsize / 2;
3611 box1 = boxCreate(x, y, tsize, tsize);
3612 boxaAddBox(boxa, box1, L_INSERT);
3613 }
3614 }
3615 } else { /* L_VERT */
3616 /* Find the tile parameters for the search */
3617 top = by; /* distance above box */
3618 bot = h - by - bh + 1; /* distance below box */
3619 h_avail = L_MAX(top, bot) - mindist;
3620 if (h_avail < tsize) {
3621 L_ERROR("tsize = %d, h_avail = %d\n", __func__, tsize, h_avail);
3622 return NULL;
3623 }
3624 h_needed = tsize + (ntiles - 1) * (tsize / 2);
3625 if (h_needed > h_avail) {
3626 t_avail = 1 + 2 * (h_avail - tsize) / tsize;
3627 L_WARNING("ntiles = %d; room for only %d\n", __func__,
3628 ntiles, t_avail);
3629 ntiles = t_avail;
3630 h_needed = tsize + (ntiles - 1) * (tsize / 2);
3631 }
3632 ncols = L_MAX(1, 1 + 2 * (bw - tsize) / tsize);
3633
3634 /* Generate the tile regions to search */
3635 boxa = boxaCreate(0);
3636 if (top > bot) /* search above */
3637 y0 = by - h_needed;
3638 else /* search below */
3639 y0 = by + bh + mindist;
3640 for (j = 0; j < ncols; j++) {
3641 x = bx + j * tsize / 2;
3642 for (i = 0; i < ntiles; i++) {
3643 y = y0 + i * tsize / 2;
3644 box1 = boxCreate(x, y, tsize, tsize);
3645 boxaAddBox(boxa, box1, L_INSERT);
3646 }
3647 }
3648 }
3649 return boxa;
3650 }