comparison mupdf-source/thirdparty/leptonica/src/adaptmap.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 adaptmap.c
29 * <pre>
30 *
31 * -------------------------------------------------------------------
32 *
33 * Image binarization algorithms are found in:
34 * grayquant.c: standard, simple, general grayscale quantization
35 * adaptmap.c: local adaptive; mostly gray-to-gray in preparation
36 * for binarization
37 * binarize.c: special binarization methods, locally adaptive.
38 * pageseg.c: locally adaptive cleaning operation with several options
39 *
40 * -------------------------------------------------------------------
41 *
42 * Clean background to white using background normalization
43 * PIX *pixCleanBackgroundToWhite()
44 *
45 * Adaptive background normalization (top-level functions)
46 * PIX *pixBackgroundNormSimple() 8 and 32 bpp
47 * PIX *pixBackgroundNorm() 8 and 32 bpp
48 * PIX *pixBackgroundNormMorph() 8 and 32 bpp
49 *
50 * Arrays of inverted background values for normalization (16 bpp)
51 * l_int32 pixBackgroundNormGrayArray() 8 bpp input
52 * l_int32 pixBackgroundNormRGBArrays() 32 bpp input
53 * l_int32 pixBackgroundNormGrayArrayMorph() 8 bpp input
54 * l_int32 pixBackgroundNormRGBArraysMorph() 32 bpp input
55 *
56 * Measurement of local background
57 * l_int32 pixGetBackgroundGrayMap() 8 bpp
58 * l_int32 pixGetBackgroundRGBMap() 32 bpp
59 * l_int32 pixGetBackgroundGrayMapMorph() 8 bpp
60 * l_int32 pixGetBackgroundRGBMapMorph() 32 bpp
61 * l_int32 pixFillMapHoles()
62 * PIX *pixExtendByReplication() 8 bpp
63 * l_int32 pixSmoothConnectedRegions() 8 bpp
64 *
65 * Measurement of local foreground
66 * l_int32 pixGetForegroundGrayMap() 8 bpp
67 *
68 * Generate inverted background map for each component
69 * PIX *pixGetInvBackgroundMap() 16 bpp
70 *
71 * Apply inverse background map to image
72 * PIX *pixApplyInvBackgroundGrayMap() 8 bpp
73 * PIX *pixApplyInvBackgroundRGBMap() 32 bpp
74 *
75 * Apply variable map
76 * PIX *pixApplyVariableGrayMap() 8 bpp
77 *
78 * Non-adaptive (global) mapping
79 * PIX *pixGlobalNormRGB() 32 bpp or cmapped
80 * PIX *pixGlobalNormNoSatRGB() 32 bpp
81 *
82 * Adaptive threshold spread normalization
83 * l_int32 pixThresholdSpreadNorm() 8 bpp
84 *
85 * Adaptive background normalization (flexible adaptaption)
86 * PIX *pixBackgroundNormFlex() 8 bpp
87 *
88 * Adaptive contrast normalization
89 * PIX *pixContrastNorm() 8 bpp
90 * static l_int32 pixMinMaxTiles()
91 * static l_int32 pixSetLowContrast()
92 * static PIX *pixLinearTRCTiled()
93 * static l_int32 *iaaGetLinearTRC()
94 *
95 * Adaptive normalization with MinMax conversion of RGB to gray,
96 * contrast enhancement and optional 2x upscale binarization
97 * PIX *pixBackgroundNormTo1MinMax()
98 * PIX *pixConvertTo8MinMax()
99 * static l_int32 *pixSelectiveContrastMod()
100 *
101 * Background normalization is done by generating a reduced map (or set
102 * of maps) representing the estimated background value of the
103 * input image, and using this to shift the pixel values so that
104 * this background value is set to some constant value.
105 *
106 * Specifically, normalization has 3 steps:
107 * (1) Generate a background map at a reduced scale.
108 * (2) Make the array of inverted background values by inverting
109 * the map. The result is an array of local multiplicative factors.
110 * (3) Apply this inverse background map to the image
111 *
112 * The inverse background arrays can be generated in two different ways here:
113 * (1) Remove the 'foreground' pixels and average over the remaining
114 * pixels in each tile. Propagate values into tiles where
115 * values have not been assigned, either because there was not
116 * enough background in the tile or because the tile is covered
117 * by a foreground region described by an image mask.
118 * After the background map is made, the inverse map is generated by
119 * smoothing over some number of adjacent tiles
120 * (block convolution) and then inverting.
121 * (2) Remove the foreground pixels using a morphological closing
122 * on a subsampled version of the image. Propagate values
123 * into pixels covered by an optional image mask. Invert the
124 * background map without preconditioning by convolutional smoothing.
125 *
126 * Other methods for adaptively normalizing the image are also given here.
127 *
128 * (1) pixThresholdSpreadNorm() computes a local threshold over the image
129 * and normalizes the input pixel values so that this computed threshold
130 * is a constant across the entire image.
131 *
132 * (2) pixContrastNorm() computes and applies a local TRC so that the
133 * local dynamic range is expanded to the full 8 bits, where the
134 * darkest pixels are mapped to 0 and the lightest to 255. This is
135 * useful for improving the appearance of pages with very light
136 * foreground or very dark background, and where the local TRC
137 * function doesn't change rapidly with position.
138 *
139 * Adaptive binarization is done in two steps:
140 * (1) Background normalization by some method
141 * (2) Global thresholding with a value appropriate to the normalization.
142 * There are several high-level functions in leptonica for doing adaptive
143 * binarization on grayscale and color images, such as:
144 * * pixAdaptThresholdToBinary() (in grayquant.c)
145 * * pixConvertTo1Adaptive() (in pixconv.c)
146 * * pixCleanImage() (in pageseg.c)
147 * </pre>
148 */
149
150 #ifdef HAVE_CONFIG_H
151 #include <config_auto.h>
152 #endif /* HAVE_CONFIG_H */
153
154 #include "allheaders.h"
155
156 /* Default input parameters for pixBackgroundNormSimple()
157 * Notes:
158 * (1) mincount must never exceed the tile area (width * height)
159 * (2) bgval must be sufficiently below 255 to avoid accidental
160 * saturation; otherwise it should be large to avoid
161 * shrinking the dynamic range
162 * (3) results should otherwise not be sensitive to these values
163 */
164 static const l_int32 DefaultTileWidth = 10; /*!< default tile width */
165 static const l_int32 DefaultTileHeight = 15; /*!< default tile height */
166 static const l_int32 DefaultFgThreshold = 60; /*!< default fg threshold */
167 static const l_int32 DefaultMinCount = 40; /*!< default minimum count */
168 static const l_int32 DefaultBgVal = 200; /*!< default bg value */
169 static const l_int32 DefaultXSmoothSize = 2; /*!< default x smooth size */
170 static const l_int32 DefaultYSmoothSize = 1; /*!< default y smooth size */
171
172 static l_int32 pixMinMaxTiles(PIX *pixs, l_int32 sx, l_int32 sy,
173 l_int32 mindiff, l_int32 smoothx, l_int32 smoothy,
174 PIX **ppixmin, PIX **ppixmax);
175 static l_int32 pixSetLowContrast(PIX *pixs1, PIX *pixs2, l_int32 mindiff);
176 static PIX *pixLinearTRCTiled(PIX *pixd, PIX *pixs, l_int32 sx, l_int32 sy,
177 PIX *pixmin, PIX *pixmax);
178 static l_int32 *iaaGetLinearTRC(l_int32 **iaa, l_int32 diff);
179
180 static l_ok pixSelectiveContrastMod(PIX *pixs, l_int32 contrast);
181
182 #ifndef NO_CONSOLE_IO
183 #define DEBUG_GLOBAL 0 /*!< set to 1 to debug pixGlobalNormNoSatRGB() */
184 #endif /* ~NO_CONSOLE_IO */
185
186 /*------------------------------------------------------------------*
187 * Clean background to white using background normalization *
188 *------------------------------------------------------------------*/
189 /*!
190 * \brief pixCleanBackgroundToWhite()
191 *
192 * \param[in] pixs 8 bpp grayscale or 32 bpp rgb
193 * \param[in] pixim [optional] 1 bpp 'image' mask; can be null
194 * \param[in] pixg [optional] 8 bpp grayscale version; can be null
195 * \param[in] gamma gamma correction; must be > 0.0; typically ~1.0
196 * \param[in] blackval dark value to set to black (0)
197 * \param[in] whiteval light value to set to white (255)
198 * \return pixd 8 bpp or 32 bpp rgb, or NULL on error
199 *
200 * <pre>
201 * Notes:
202 * (1) This is a simplified interface for cleaning an image.
203 * For comparison, see pixAdaptThresholdToBinaryGen().
204 * (2) The suggested default values for the input parameters are:
205 * gamma: 1.0 (reduce this to increase the contrast; e.g.,
206 * for light text)
207 * blackval 70 (a bit more than 60)
208 * whiteval 190 (a bit less than 200)
209 * (3) Note: the whiteval must not exceed 200, which is the value
210 * that the background is set to in pixBackgroundNormSimple().
211 * </pre>
212 */
213 PIX *
214 pixCleanBackgroundToWhite(PIX *pixs,
215 PIX *pixim,
216 PIX *pixg,
217 l_float32 gamma,
218 l_int32 blackval,
219 l_int32 whiteval)
220 {
221 l_int32 d;
222 PIX *pixd;
223
224 if (!pixs)
225 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
226 d = pixGetDepth(pixs);
227 if (d != 8 && d != 32)
228 return (PIX *)ERROR_PTR("depth not 8 or 32", __func__, NULL);
229 if (whiteval > 200) {
230 L_WARNING("white value %d must not exceed 200; reset to 190",
231 __func__, whiteval);
232 whiteval = 190;
233 }
234
235 pixd = pixBackgroundNormSimple(pixs, pixim, pixg);
236 if (!pixd)
237 return (PIX *)ERROR_PTR("background norm failedd", __func__, NULL);
238 pixGammaTRC(pixd, pixd, gamma, blackval, whiteval);
239 return pixd;
240 }
241
242
243 /*------------------------------------------------------------------*
244 * Adaptive background normalization *
245 *------------------------------------------------------------------*/
246 /*!
247 * \brief pixBackgroundNormSimple()
248 *
249 * \param[in] pixs 8 bpp grayscale or 32 bpp rgb
250 * \param[in] pixim [optional] 1 bpp 'image' mask; can be null
251 * \param[in] pixg [optional] 8 bpp grayscale version; can be null
252 * \return pixd 8 bpp or 32 bpp rgb, or NULL on error
253 *
254 * <pre>
255 * Notes:
256 * (1) This is a simplified interface to pixBackgroundNorm(),
257 * where seven parameters are defaulted.
258 * (2) The input image is either grayscale or rgb.
259 * (3) See pixBackgroundNorm() for usage and function.
260 * </pre>
261 */
262 PIX *
263 pixBackgroundNormSimple(PIX *pixs,
264 PIX *pixim,
265 PIX *pixg)
266 {
267 return pixBackgroundNorm(pixs, pixim, pixg,
268 DefaultTileWidth, DefaultTileHeight,
269 DefaultFgThreshold, DefaultMinCount,
270 DefaultBgVal, DefaultXSmoothSize,
271 DefaultYSmoothSize);
272 }
273
274
275 /*!
276 * \brief pixBackgroundNorm()
277 *
278 * \param[in] pixs 8 bpp grayscale or 32 bpp rgb
279 * \param[in] pixim [optional] 1 bpp 'image' mask; can be null
280 * \param[in] pixg [optional] 8 bpp grayscale version; can be null
281 * \param[in] sx, sy tile size in pixels
282 * \param[in] thresh threshold for determining foreground
283 * \param[in] mincount min threshold on counts in a tile
284 * \param[in] bgval target bg val; typ. > 128
285 * \param[in] smoothx half-width of block convolution kernel width
286 * \param[in] smoothy half-width of block convolution kernel height
287 * \return pixd 8 bpp or 32 bpp rgb, or NULL on error
288 *
289 * <pre>
290 * Notes:
291 * (1) This is a top-level interface for normalizing the image intensity
292 * by mapping the image so that the background is near the input
293 * value %bgval.
294 * (2) The input image is either grayscale or rgb.
295 * (3) For each component in the input image, the background value
296 * in each tile is estimated using the values in the tile that
297 * are not part of the foreground, where the foreground is
298 * determined by %thresh.
299 * (4) An optional binary mask can be specified, with the foreground
300 * pixels typically over image regions. The resulting background
301 * map values will be determined by surrounding pixels that are
302 * not under the mask foreground. The origin (0,0) of this mask
303 * is assumed to be aligned with the origin of the input image.
304 * This binary mask must not fully cover pixs, because then there
305 * will be no pixels in the input image available to compute
306 * the background.
307 * (5) An optional grayscale version of the input pixs can be supplied.
308 * The only reason to do this is if the input is RGB and this
309 * grayscale version can be used elsewhere. If the input is RGB
310 * and this is not supplied, it is made internally using only
311 * the green component, and destroyed after use.
312 * (6) The dimensions of the pixel tile (%sx, %sy) give the amount
313 * by which the map is reduced in size from the input image.
314 * (7) The input image is binarized using %thresh, in order to
315 * locate the foreground components. If this is set too low,
316 * some actual foreground may be used to determine the maps;
317 * if set too high, there may not be enough background
318 * to determine the map values accurately. Typically, it is
319 * better to err by setting the threshold too high.
320 * (8) A %mincount threshold is a minimum count of pixels in a
321 * tile for which a background reading is made, in order for that
322 * pixel in the map to be valid. This number should perhaps be
323 * at least 1/3 the size of the tile.
324 * (9) A %bgval target background value for the normalized image. This
325 * should be at least 128. If set too close to 255, some
326 * clipping will occur in the result. It is recommended to use
327 * %bgval = 200.
328 * (10) Two factors, %smoothx and %smoothy, are input for smoothing
329 * the map. Each low-pass filter kernel dimension is
330 * is 2 * (smoothing factor) + 1, so a
331 * value of 0 means no smoothing. A value of 1 or 2 is recommended.
332 * (11) See pixCleanBackgroundToWhite(). The recommended value for %bgval
333 * is 200. As done there, pixBackgroundNorm() is typically followed
334 * by pixGammaTRC(), where the maxval must not not exceed %bgval.
335 * </pre>
336 */
337 PIX *
338 pixBackgroundNorm(PIX *pixs,
339 PIX *pixim,
340 PIX *pixg,
341 l_int32 sx,
342 l_int32 sy,
343 l_int32 thresh,
344 l_int32 mincount,
345 l_int32 bgval,
346 l_int32 smoothx,
347 l_int32 smoothy)
348 {
349 l_int32 d, allfg;
350 PIX *pixm, *pixmi, *pixd;
351 PIX *pixmr, *pixmg, *pixmb, *pixmri, *pixmgi, *pixmbi;
352
353 if (!pixs)
354 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
355 d = pixGetDepth(pixs);
356 if (d != 8 && d != 32)
357 return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", __func__, NULL);
358 if (sx < 4 || sy < 4)
359 return (PIX *)ERROR_PTR("sx and sy must be >= 4", __func__, NULL);
360 if (mincount > sx * sy) {
361 L_WARNING("mincount too large for tile size\n", __func__);
362 mincount = (sx * sy) / 3;
363 }
364
365 /* If pixim exists, verify that it is not all foreground. */
366 if (pixim) {
367 pixInvert(pixim, pixim);
368 pixZero(pixim, &allfg);
369 pixInvert(pixim, pixim);
370 if (allfg)
371 return (PIX *)ERROR_PTR("pixim all foreground", __func__, NULL);
372 }
373
374 pixd = NULL;
375 if (d == 8) {
376 pixm = NULL;
377 pixGetBackgroundGrayMap(pixs, pixim, sx, sy, thresh, mincount, &pixm);
378 if (!pixm) {
379 L_WARNING("map not made; return a copy of the source\n", __func__);
380 return pixCopy(NULL, pixs);
381 }
382
383 pixmi = pixGetInvBackgroundMap(pixm, bgval, smoothx, smoothy);
384 if (!pixmi) {
385 L_WARNING("pixmi not made; return a copy of source\n", __func__);
386 pixDestroy(&pixm);
387 return pixCopy(NULL, pixs);
388 } else {
389 pixd = pixApplyInvBackgroundGrayMap(pixs, pixmi, sx, sy);
390 }
391
392 pixDestroy(&pixm);
393 pixDestroy(&pixmi);
394 }
395 else {
396 pixmr = pixmg = pixmb = NULL;
397 pixGetBackgroundRGBMap(pixs, pixim, pixg, sx, sy, thresh,
398 mincount, &pixmr, &pixmg, &pixmb);
399 if (!pixmr || !pixmg || !pixmb) {
400 pixDestroy(&pixmr);
401 pixDestroy(&pixmg);
402 pixDestroy(&pixmb);
403 L_WARNING("map not made; return a copy of the source\n", __func__);
404 return pixCopy(NULL, pixs);
405 }
406
407 pixmri = pixGetInvBackgroundMap(pixmr, bgval, smoothx, smoothy);
408 pixmgi = pixGetInvBackgroundMap(pixmg, bgval, smoothx, smoothy);
409 pixmbi = pixGetInvBackgroundMap(pixmb, bgval, smoothx, smoothy);
410 if (!pixmri || !pixmgi || !pixmbi) {
411 L_WARNING("not all pixm*i are made; return src copy\n", __func__);
412 pixd = pixCopy(NULL, pixs);
413 } else {
414 pixd = pixApplyInvBackgroundRGBMap(pixs, pixmri, pixmgi, pixmbi,
415 sx, sy);
416 }
417
418 pixDestroy(&pixmr);
419 pixDestroy(&pixmg);
420 pixDestroy(&pixmb);
421 pixDestroy(&pixmri);
422 pixDestroy(&pixmgi);
423 pixDestroy(&pixmbi);
424 }
425
426 if (!pixd)
427 ERROR_PTR("pixd not made", __func__, NULL);
428 pixCopyResolution(pixd, pixs);
429 return pixd;
430 }
431
432
433 /*!
434 * \brief pixBackgroundNormMorph()
435 *
436 * \param[in] pixs 8 bpp grayscale or 32 bpp rgb
437 * \param[in] pixim [optional] 1 bpp 'image' mask; can be null
438 * \param[in] reduction at which morph closings are done; between 2 and 16
439 * \param[in] size of square Sel for the closing; use an odd number
440 * \param[in] bgval target bg val; typ. > 128
441 * \return pixd 8 bpp, or NULL on error
442 *
443 * <pre>
444 * Notes:
445 * (1) This is a top-level interface for normalizing the image intensity
446 * by mapping the image so that the background is near the input
447 * value 'bgval'.
448 * (2) The input image is either grayscale or rgb.
449 * (3) For each component in the input image, the background value
450 * is estimated using a grayscale closing; hence the 'Morph'
451 * in the function name.
452 * (4) An optional binary mask can be specified, with the foreground
453 * pixels typically over image regions. The resulting background
454 * map values will be determined by surrounding pixels that are
455 * not under the mask foreground. The origin (0,0) of this mask
456 * is assumed to be aligned with the origin of the input image.
457 * This binary mask must not fully cover pixs, because then there
458 * will be no pixels in the input image available to compute
459 * the background.
460 * (5) The map is computed at reduced size (given by 'reduction')
461 * from the input pixs and optional pixim. At this scale,
462 * pixs is closed to remove the background, using a square Sel
463 * of odd dimension. The product of reduction * size should be
464 * large enough to remove most of the text foreground.
465 * (6) No convolutional smoothing needs to be done on the map before
466 * inverting it.
467 * (7) A 'bgval' target background value for the normalized image. This
468 * should be at least 128. If set too close to 255, some
469 * clipping will occur in the result.
470 * </pre>
471 */
472 PIX *
473 pixBackgroundNormMorph(PIX *pixs,
474 PIX *pixim,
475 l_int32 reduction,
476 l_int32 size,
477 l_int32 bgval)
478 {
479 l_int32 d, allfg;
480 PIX *pixm, *pixmi, *pixd;
481 PIX *pixmr, *pixmg, *pixmb, *pixmri, *pixmgi, *pixmbi;
482
483 if (!pixs)
484 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
485 d = pixGetDepth(pixs);
486 if (d != 8 && d != 32)
487 return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", __func__, NULL);
488 if (reduction < 2 || reduction > 16)
489 return (PIX *)ERROR_PTR("reduction must be between 2 and 16",
490 __func__, NULL);
491
492 /* If pixim exists, verify that it is not all foreground. */
493 if (pixim) {
494 pixInvert(pixim, pixim);
495 pixZero(pixim, &allfg);
496 pixInvert(pixim, pixim);
497 if (allfg)
498 return (PIX *)ERROR_PTR("pixim all foreground", __func__, NULL);
499 }
500
501 pixd = NULL;
502 if (d == 8) {
503 pixGetBackgroundGrayMapMorph(pixs, pixim, reduction, size, &pixm);
504 if (!pixm)
505 return (PIX *)ERROR_PTR("pixm not made", __func__, NULL);
506 pixmi = pixGetInvBackgroundMap(pixm, bgval, 0, 0);
507 if (!pixmi)
508 ERROR_PTR("pixmi not made", __func__, NULL);
509 else
510 pixd = pixApplyInvBackgroundGrayMap(pixs, pixmi,
511 reduction, reduction);
512 pixDestroy(&pixm);
513 pixDestroy(&pixmi);
514 }
515 else { /* d == 32 */
516 pixmr = pixmg = pixmb = NULL;
517 pixGetBackgroundRGBMapMorph(pixs, pixim, reduction, size,
518 &pixmr, &pixmg, &pixmb);
519 if (!pixmr || !pixmg || !pixmb) {
520 pixDestroy(&pixmr);
521 pixDestroy(&pixmg);
522 pixDestroy(&pixmb);
523 return (PIX *)ERROR_PTR("not all pixm*", __func__, NULL);
524 }
525
526 pixmri = pixGetInvBackgroundMap(pixmr, bgval, 0, 0);
527 pixmgi = pixGetInvBackgroundMap(pixmg, bgval, 0, 0);
528 pixmbi = pixGetInvBackgroundMap(pixmb, bgval, 0, 0);
529 if (!pixmri || !pixmgi || !pixmbi)
530 ERROR_PTR("not all pixm*i are made", __func__, NULL);
531 else
532 pixd = pixApplyInvBackgroundRGBMap(pixs, pixmri, pixmgi, pixmbi,
533 reduction, reduction);
534
535 pixDestroy(&pixmr);
536 pixDestroy(&pixmg);
537 pixDestroy(&pixmb);
538 pixDestroy(&pixmri);
539 pixDestroy(&pixmgi);
540 pixDestroy(&pixmbi);
541 }
542
543 if (!pixd)
544 ERROR_PTR("pixd not made", __func__, NULL);
545 pixCopyResolution(pixd, pixs);
546 return pixd;
547 }
548
549
550 /*-------------------------------------------------------------------------*
551 * Arrays of inverted background values for normalization *
552 *-------------------------------------------------------------------------*
553 * Notes for these four functions: *
554 * (1) They are useful if you need to save the actual mapping array. *
555 * (2) They could be used in the top-level functions but are *
556 * not because their use makes those functions less clear. *
557 * (3) Each component in the input pixs generates a 16 bpp pix array. *
558 *-------------------------------------------------------------------------*/
559 /*!
560 * \brief pixBackgroundNormGrayArray()
561 *
562 * \param[in] pixs 8 bpp grayscale
563 * \param[in] pixim [optional] 1 bpp 'image' mask; can be null
564 * \param[in] sx, sy tile size in pixels
565 * \param[in] thresh threshold for determining foreground
566 * \param[in] mincount min threshold on counts in a tile
567 * \param[in] bgval target bg val; typ. > 128
568 * \param[in] smoothx half-width of block convolution kernel width
569 * \param[in] smoothy half-width of block convolution kernel height
570 * \param[out] ppixd 16 bpp array of inverted background value
571 * \return 0 if OK, 1 on error
572 *
573 * <pre>
574 * Notes:
575 * (1) See notes in pixBackgroundNorm().
576 * (2) This returns a 16 bpp pix that can be used by
577 * pixApplyInvBackgroundGrayMap() to generate a normalized version
578 * of the input pixs.
579 * </pre>
580 */
581 l_ok
582 pixBackgroundNormGrayArray(PIX *pixs,
583 PIX *pixim,
584 l_int32 sx,
585 l_int32 sy,
586 l_int32 thresh,
587 l_int32 mincount,
588 l_int32 bgval,
589 l_int32 smoothx,
590 l_int32 smoothy,
591 PIX **ppixd)
592 {
593 l_int32 allfg;
594 PIX *pixm;
595
596 if (!ppixd)
597 return ERROR_INT("&pixd not defined", __func__, 1);
598 *ppixd = NULL;
599 if (!pixs || pixGetDepth(pixs) != 8)
600 return ERROR_INT("pixs not defined or not 8 bpp", __func__, 1);
601 if (pixGetColormap(pixs))
602 return ERROR_INT("pixs is colormapped", __func__, 1);
603 if (pixim && pixGetDepth(pixim) != 1)
604 return ERROR_INT("pixim not 1 bpp", __func__, 1);
605 if (sx < 4 || sy < 4)
606 return ERROR_INT("sx and sy must be >= 4", __func__, 1);
607 if (mincount > sx * sy) {
608 L_WARNING("mincount too large for tile size\n", __func__);
609 mincount = (sx * sy) / 3;
610 }
611
612 /* If pixim exists, verify that it is not all foreground. */
613 if (pixim) {
614 pixInvert(pixim, pixim);
615 pixZero(pixim, &allfg);
616 pixInvert(pixim, pixim);
617 if (allfg)
618 return ERROR_INT("pixim all foreground", __func__, 1);
619 }
620
621 pixGetBackgroundGrayMap(pixs, pixim, sx, sy, thresh, mincount, &pixm);
622 if (!pixm)
623 return ERROR_INT("pixm not made", __func__, 1);
624 *ppixd = pixGetInvBackgroundMap(pixm, bgval, smoothx, smoothy);
625 pixCopyResolution(*ppixd, pixs);
626 pixDestroy(&pixm);
627 return 0;
628 }
629
630
631 /*!
632 * \brief pixBackgroundNormRGBArrays()
633 *
634 * \param[in] pixs 32 bpp rgb
635 * \param[in] pixim [optional] 1 bpp 'image' mask; can be null
636 * \param[in] pixg [optional] 8 bpp grayscale version; can be null
637 * \param[in] sx, sy tile size in pixels
638 * \param[in] thresh threshold for determining foreground
639 * \param[in] mincount min threshold on counts in a tile
640 * \param[in] bgval target bg val; typ. > 128
641 * \param[in] smoothx half-width of block convolution kernel width
642 * \param[in] smoothy half-width of block convolution kernel height
643 * \param[out] ppixr 16 bpp array of inverted R background value
644 * \param[out] ppixg 16 bpp array of inverted G background value
645 * \param[out] ppixb 16 bpp array of inverted B background value
646 * \return 0 if OK, 1 on error
647 *
648 * <pre>
649 * Notes:
650 * (1) See notes in pixBackgroundNorm().
651 * (2) This returns a set of three 16 bpp pix that can be used by
652 * pixApplyInvBackgroundGrayMap() to generate a normalized version
653 * of each component of the input pixs.
654 * </pre>
655 */
656 l_ok
657 pixBackgroundNormRGBArrays(PIX *pixs,
658 PIX *pixim,
659 PIX *pixg,
660 l_int32 sx,
661 l_int32 sy,
662 l_int32 thresh,
663 l_int32 mincount,
664 l_int32 bgval,
665 l_int32 smoothx,
666 l_int32 smoothy,
667 PIX **ppixr,
668 PIX **ppixg,
669 PIX **ppixb)
670 {
671 l_int32 allfg;
672 PIX *pixmr, *pixmg, *pixmb;
673
674 if (!ppixr || !ppixg || !ppixb)
675 return ERROR_INT("&pixr, &pixg, &pixb not all defined", __func__, 1);
676 *ppixr = *ppixg = *ppixb = NULL;
677 if (!pixs)
678 return ERROR_INT("pixs not defined", __func__, 1);
679 if (pixGetDepth(pixs) != 32)
680 return ERROR_INT("pixs not 32 bpp", __func__, 1);
681 if (pixim && pixGetDepth(pixim) != 1)
682 return ERROR_INT("pixim not 1 bpp", __func__, 1);
683 if (sx < 4 || sy < 4)
684 return ERROR_INT("sx and sy must be >= 4", __func__, 1);
685 if (mincount > sx * sy) {
686 L_WARNING("mincount too large for tile size\n", __func__);
687 mincount = (sx * sy) / 3;
688 }
689
690 /* If pixim exists, verify that it is not all foreground. */
691 if (pixim) {
692 pixInvert(pixim, pixim);
693 pixZero(pixim, &allfg);
694 pixInvert(pixim, pixim);
695 if (allfg)
696 return ERROR_INT("pixim all foreground", __func__, 1);
697 }
698
699 pixGetBackgroundRGBMap(pixs, pixim, pixg, sx, sy, thresh, mincount,
700 &pixmr, &pixmg, &pixmb);
701 if (!pixmr || !pixmg || !pixmb) {
702 pixDestroy(&pixmr);
703 pixDestroy(&pixmg);
704 pixDestroy(&pixmb);
705 return ERROR_INT("not all pixm* made", __func__, 1);
706 }
707
708 *ppixr = pixGetInvBackgroundMap(pixmr, bgval, smoothx, smoothy);
709 *ppixg = pixGetInvBackgroundMap(pixmg, bgval, smoothx, smoothy);
710 *ppixb = pixGetInvBackgroundMap(pixmb, bgval, smoothx, smoothy);
711 pixDestroy(&pixmr);
712 pixDestroy(&pixmg);
713 pixDestroy(&pixmb);
714 return 0;
715 }
716
717
718 /*!
719 * \brief pixBackgroundNormGrayArrayMorph()
720 *
721 * \param[in] pixs 8 bpp grayscale
722 * \param[in] pixim [optional] 1 bpp 'image' mask; can be null
723 * \param[in] reduction at which morph closings are done; between 2 and 16
724 * \param[in] size of square Sel for the closing; use an odd number
725 * \param[in] bgval target bg val; typ. > 128
726 * \param[out] ppixd 16 bpp array of inverted background value
727 * \return 0 if OK, 1 on error
728 *
729 * <pre>
730 * Notes:
731 * (1) See notes in pixBackgroundNormMorph().
732 * (2) This returns a 16 bpp pix that can be used by
733 * pixApplyInvBackgroundGrayMap() to generate a normalized version
734 * of the input pixs.
735 * </pre>
736 */
737 l_ok
738 pixBackgroundNormGrayArrayMorph(PIX *pixs,
739 PIX *pixim,
740 l_int32 reduction,
741 l_int32 size,
742 l_int32 bgval,
743 PIX **ppixd)
744 {
745 l_int32 allfg;
746 PIX *pixm;
747
748 if (!ppixd)
749 return ERROR_INT("&pixd not defined", __func__, 1);
750 *ppixd = NULL;
751 if (!pixs)
752 return ERROR_INT("pixs not defined", __func__, 1);
753 if (pixGetDepth(pixs) != 8)
754 return ERROR_INT("pixs not 8 bpp", __func__, 1);
755 if (pixim && pixGetDepth(pixim) != 1)
756 return ERROR_INT("pixim not 1 bpp", __func__, 1);
757 if (reduction < 2 || reduction > 16)
758 return ERROR_INT("reduction must be between 2 and 16", __func__, 1);
759
760 /* If pixim exists, verify that it is not all foreground. */
761 if (pixim) {
762 pixInvert(pixim, pixim);
763 pixZero(pixim, &allfg);
764 pixInvert(pixim, pixim);
765 if (allfg)
766 return ERROR_INT("pixim all foreground", __func__, 1);
767 }
768
769 pixGetBackgroundGrayMapMorph(pixs, pixim, reduction, size, &pixm);
770 if (!pixm)
771 return ERROR_INT("pixm not made", __func__, 1);
772 *ppixd = pixGetInvBackgroundMap(pixm, bgval, 0, 0);
773 pixCopyResolution(*ppixd, pixs);
774 pixDestroy(&pixm);
775 return 0;
776 }
777
778
779 /*!
780 * \brief pixBackgroundNormRGBArraysMorph()
781 *
782 * \param[in] pixs 32 bpp rgb
783 * \param[in] pixim [optional] 1 bpp 'image' mask; can be null
784 * \param[in] reduction at which morph closings are done; between 2 and 16
785 * \param[in] size of square Sel for the closing; use an odd number
786 * \param[in] bgval target bg val; typ. > 128
787 * \param[out] ppixr 16 bpp array of inverted R background value
788 * \param[out] ppixg 16 bpp array of inverted G background value
789 * \param[out] ppixb 16 bpp array of inverted B background value
790 * \return 0 if OK, 1 on error
791 *
792 * <pre>
793 * Notes:
794 * (1) See notes in pixBackgroundNormMorph().
795 * (2) This returns a set of three 16 bpp pix that can be used by
796 * pixApplyInvBackgroundGrayMap() to generate a normalized version
797 * of each component of the input pixs.
798 * </pre>
799 */
800 l_ok
801 pixBackgroundNormRGBArraysMorph(PIX *pixs,
802 PIX *pixim,
803 l_int32 reduction,
804 l_int32 size,
805 l_int32 bgval,
806 PIX **ppixr,
807 PIX **ppixg,
808 PIX **ppixb)
809 {
810 l_int32 allfg;
811 PIX *pixmr, *pixmg, *pixmb;
812
813 if (!ppixr || !ppixg || !ppixb)
814 return ERROR_INT("&pixr, &pixg, &pixb not all defined", __func__, 1);
815 *ppixr = *ppixg = *ppixb = NULL;
816 if (!pixs)
817 return ERROR_INT("pixs not defined", __func__, 1);
818 if (pixGetDepth(pixs) != 32)
819 return ERROR_INT("pixs not 32 bpp", __func__, 1);
820 if (pixim && pixGetDepth(pixim) != 1)
821 return ERROR_INT("pixim not 1 bpp", __func__, 1);
822 if (reduction < 2 || reduction > 16)
823 return ERROR_INT("reduction must be between 2 and 16", __func__, 1);
824
825 /* If pixim exists, verify that it is not all foreground. */
826 if (pixim) {
827 pixInvert(pixim, pixim);
828 pixZero(pixim, &allfg);
829 pixInvert(pixim, pixim);
830 if (allfg)
831 return ERROR_INT("pixim all foreground", __func__, 1);
832 }
833
834 pixGetBackgroundRGBMapMorph(pixs, pixim, reduction, size,
835 &pixmr, &pixmg, &pixmb);
836 if (!pixmr || !pixmg || !pixmb) {
837 pixDestroy(&pixmr);
838 pixDestroy(&pixmg);
839 pixDestroy(&pixmb);
840 return ERROR_INT("not all pixm* made", __func__, 1);
841 }
842
843 *ppixr = pixGetInvBackgroundMap(pixmr, bgval, 0, 0);
844 *ppixg = pixGetInvBackgroundMap(pixmg, bgval, 0, 0);
845 *ppixb = pixGetInvBackgroundMap(pixmb, bgval, 0, 0);
846 pixDestroy(&pixmr);
847 pixDestroy(&pixmg);
848 pixDestroy(&pixmb);
849 return 0;
850 }
851
852
853 /*------------------------------------------------------------------*
854 * Measurement of local background *
855 *------------------------------------------------------------------*/
856 /*!
857 * \brief pixGetBackgroundGrayMap()
858 *
859 * \param[in] pixs 8 bpp grayscale; not cmapped
860 * \param[in] pixim [optional] 1 bpp 'image' mask; can be null;
861 * it should not have only foreground pixels
862 * \param[in] sx, sy tile size in pixels
863 * \param[in] thresh threshold for determining foreground
864 * \param[in] mincount min threshold on counts in a tile
865 * \param[out] ppixd 8 bpp grayscale map
866 * \return 0 if OK, 1 on error
867 *
868 * <pre>
869 * Notes:
870 * (1) The background is measured in regions that don't have
871 * images. It is then propagated into the image regions,
872 * and finally smoothed in each image region.
873 * </pre>
874 */
875 l_ok
876 pixGetBackgroundGrayMap(PIX *pixs,
877 PIX *pixim,
878 l_int32 sx,
879 l_int32 sy,
880 l_int32 thresh,
881 l_int32 mincount,
882 PIX **ppixd)
883 {
884 l_int32 w, h, wd, hd, wim, him, wpls, wplim, wpld, wplf;
885 l_int32 xim, yim, delx, nx, ny, i, j, k, m;
886 l_int32 count, sum, val8;
887 l_int32 empty, fgpixels;
888 l_uint32 *datas, *dataim, *datad, *dataf, *lines, *lineim, *lined, *linef;
889 l_float32 scalex, scaley;
890 PIX *pixd, *piximi, *pixb, *pixf, *pixims;
891
892 if (!ppixd)
893 return ERROR_INT("&pixd not defined", __func__, 1);
894 *ppixd = NULL;
895 if (!pixs || pixGetDepth(pixs) != 8)
896 return ERROR_INT("pixs not defined or not 8 bpp", __func__, 1);
897 if (pixGetColormap(pixs))
898 return ERROR_INT("pixs is colormapped", __func__, 1);
899 if (pixim && pixGetDepth(pixim) != 1)
900 return ERROR_INT("pixim not 1 bpp", __func__, 1);
901 if (sx < 4 || sy < 4)
902 return ERROR_INT("sx and sy must be >= 4", __func__, 1);
903 if (mincount > sx * sy) {
904 L_WARNING("mincount too large for tile size\n", __func__);
905 mincount = (sx * sy) / 3;
906 }
907
908 /* Evaluate the 'image' mask, pixim, and make sure
909 * it is not all fg. */
910 fgpixels = 0; /* boolean for existence of fg pixels in the image mask. */
911 if (pixim) {
912 piximi = pixInvert(NULL, pixim); /* set non-'image' pixels to 1 */
913 pixZero(piximi, &empty);
914 pixDestroy(&piximi);
915 if (empty)
916 return ERROR_INT("pixim all fg; no background", __func__, 1);
917 pixZero(pixim, &empty);
918 if (!empty) /* there are fg pixels in pixim */
919 fgpixels = 1;
920 }
921
922 /* Generate the foreground mask, pixf, which is at
923 * full resolution. These pixels will be ignored when
924 * computing the background values. */
925 pixb = pixThresholdToBinary(pixs, thresh);
926 pixf = pixMorphSequence(pixb, "d7.1 + d1.7", 0);
927 pixDestroy(&pixb);
928 if (!pixf)
929 return ERROR_INT("pixf not made", __func__, 1);
930
931
932 /* ------------- Set up the output map pixd --------------- */
933 /* Generate pixd, which is reduced by the factors (sx, sy). */
934 w = pixGetWidth(pixs);
935 h = pixGetHeight(pixs);
936 wd = (w + sx - 1) / sx;
937 hd = (h + sy - 1) / sy;
938 pixd = pixCreate(wd, hd, 8);
939
940 /* Note: we only compute map values in tiles that are complete.
941 * In general, tiles at right and bottom edges will not be
942 * complete, and we must fill them in later. */
943 nx = w / sx;
944 ny = h / sy;
945 wpls = pixGetWpl(pixs);
946 datas = pixGetData(pixs);
947 wpld = pixGetWpl(pixd);
948 datad = pixGetData(pixd);
949 wplf = pixGetWpl(pixf);
950 dataf = pixGetData(pixf);
951 for (i = 0; i < ny; i++) {
952 lines = datas + sy * i * wpls;
953 linef = dataf + sy * i * wplf;
954 lined = datad + i * wpld;
955 for (j = 0; j < nx; j++) {
956 delx = j * sx;
957 sum = 0;
958 count = 0;
959 for (k = 0; k < sy; k++) {
960 for (m = 0; m < sx; m++) {
961 if (GET_DATA_BIT(linef + k * wplf, delx + m) == 0) {
962 sum += GET_DATA_BYTE(lines + k * wpls, delx + m);
963 count++;
964 }
965 }
966 }
967 if (count >= mincount) {
968 val8 = sum / count;
969 SET_DATA_BYTE(lined, j, val8);
970 }
971 }
972 }
973 pixDestroy(&pixf);
974
975 /* If there is an optional mask with fg pixels, erase the previous
976 * calculation for the corresponding map pixels, setting the
977 * map values to 0. Then, when all the map holes are filled,
978 * these erased pixels will be set by the surrounding map values.
979 *
980 * The calculation here is relatively efficient: for each pixel
981 * in pixd (which corresponds to a tile of mask pixels in pixim)
982 * we look only at the pixel in pixim that is at the center
983 * of the tile. If the mask pixel is ON, we reset the map
984 * pixel in pixd to 0, so that it can later be filled in. */
985 pixims = NULL;
986 if (pixim && fgpixels) {
987 wim = pixGetWidth(pixim);
988 him = pixGetHeight(pixim);
989 dataim = pixGetData(pixim);
990 wplim = pixGetWpl(pixim);
991 for (i = 0; i < ny; i++) {
992 yim = i * sy + sy / 2;
993 if (yim >= him)
994 break;
995 lineim = dataim + yim * wplim;
996 for (j = 0; j < nx; j++) {
997 xim = j * sx + sx / 2;
998 if (xim >= wim)
999 break;
1000 if (GET_DATA_BIT(lineim, xim))
1001 pixSetPixel(pixd, j, i, 0);
1002 }
1003 }
1004 }
1005
1006 /* Fill all the holes in the map. */
1007 if (pixFillMapHoles(pixd, nx, ny, L_FILL_BLACK)) {
1008 pixDestroy(&pixd);
1009 L_WARNING("can't make the map\n", __func__);
1010 return 1;
1011 }
1012
1013 /* Finally, for each connected region corresponding to the
1014 * 'image' mask, reset all pixels to their average value.
1015 * Each of these components represents an image (or part of one)
1016 * in the input, and this smooths the background values
1017 * in each of these regions. */
1018 if (pixim && fgpixels) {
1019 scalex = 1. / (l_float32)sx;
1020 scaley = 1. / (l_float32)sy;
1021 pixims = pixScaleBySampling(pixim, scalex, scaley);
1022 pixSmoothConnectedRegions(pixd, pixims, 2);
1023 pixDestroy(&pixims);
1024 }
1025
1026 *ppixd = pixd;
1027 pixCopyResolution(*ppixd, pixs);
1028 return 0;
1029 }
1030
1031
1032 /*!
1033 * \brief pixGetBackgroundRGBMap()
1034 *
1035 * \param[in] pixs 32 bpp rgb
1036 * \param[in] pixim [optional] 1 bpp 'image' mask; can be null; it
1037 * should not have all foreground pixels
1038 * \param[in] pixg [optional] 8 bpp grayscale version; can be null
1039 * \param[in] sx, sy tile size in pixels
1040 * \param[in] thresh threshold for determining foreground
1041 * \param[in] mincount min threshold on counts in a tile
1042 * \param[out] ppixmr red component map
1043 * \param[out] ppixmg green component map
1044 * \param[out] ppixmb blue component map
1045 * \return 0 if OK, 1 on error
1046 *
1047 * <pre>
1048 * Notes:
1049 * (1) If pixg, which is a grayscale version of pixs, is provided,
1050 * use this internally to generate the foreground mask.
1051 * Otherwise, a grayscale version of pixs will be generated
1052 * from the green component only, used, and destroyed.
1053 * </pre>
1054 */
1055 l_ok
1056 pixGetBackgroundRGBMap(PIX *pixs,
1057 PIX *pixim,
1058 PIX *pixg,
1059 l_int32 sx,
1060 l_int32 sy,
1061 l_int32 thresh,
1062 l_int32 mincount,
1063 PIX **ppixmr,
1064 PIX **ppixmg,
1065 PIX **ppixmb)
1066 {
1067 l_int32 w, h, wm, hm, wim, him, wpls, wplim, wplf;
1068 l_int32 xim, yim, delx, nx, ny, i, j, k, m;
1069 l_int32 count, rsum, gsum, bsum, rval, gval, bval;
1070 l_int32 empty, fgpixels;
1071 l_uint32 pixel;
1072 l_uint32 *datas, *dataim, *dataf, *lines, *lineim, *linef;
1073 l_float32 scalex, scaley;
1074 PIX *piximi, *pixgc, *pixb, *pixf, *pixims;
1075 PIX *pixmr, *pixmg, *pixmb;
1076
1077 if (!ppixmr || !ppixmg || !ppixmb)
1078 return ERROR_INT("&pixm* not all defined", __func__, 1);
1079 *ppixmr = *ppixmg = *ppixmb = NULL;
1080 if (!pixs)
1081 return ERROR_INT("pixs not defined", __func__, 1);
1082 if (pixGetDepth(pixs) != 32)
1083 return ERROR_INT("pixs not 32 bpp", __func__, 1);
1084 if (pixim && pixGetDepth(pixim) != 1)
1085 return ERROR_INT("pixim not 1 bpp", __func__, 1);
1086 if (sx < 4 || sy < 4)
1087 return ERROR_INT("sx and sy must be >= 4", __func__, 1);
1088 if (mincount > sx * sy) {
1089 L_WARNING("mincount too large for tile size\n", __func__);
1090 mincount = (sx * sy) / 3;
1091 }
1092
1093 /* Evaluate the mask pixim and make sure it is not all foreground */
1094 fgpixels = 0; /* boolean for existence of fg mask pixels */
1095 if (pixim) {
1096 piximi = pixInvert(NULL, pixim); /* set non-'image' pixels to 1 */
1097 pixZero(piximi, &empty);
1098 pixDestroy(&piximi);
1099 if (empty)
1100 return ERROR_INT("pixim all fg; no background", __func__, 1);
1101 pixZero(pixim, &empty);
1102 if (!empty) /* there are fg pixels in pixim */
1103 fgpixels = 1;
1104 }
1105
1106 /* Generate the foreground mask. These pixels will be
1107 * ignored when computing the background values. */
1108 if (pixg) /* use the input grayscale version if it is provided */
1109 pixgc = pixClone(pixg);
1110 else
1111 pixgc = pixConvertRGBToGrayFast(pixs);
1112 pixb = pixThresholdToBinary(pixgc, thresh);
1113 pixf = pixMorphSequence(pixb, "d7.1 + d1.7", 0);
1114 pixDestroy(&pixgc);
1115 pixDestroy(&pixb);
1116
1117 /* Generate the output mask images */
1118 w = pixGetWidth(pixs);
1119 h = pixGetHeight(pixs);
1120 wm = (w + sx - 1) / sx;
1121 hm = (h + sy - 1) / sy;
1122 pixmr = pixCreate(wm, hm, 8);
1123 pixmg = pixCreate(wm, hm, 8);
1124 pixmb = pixCreate(wm, hm, 8);
1125
1126 /* ------------- Set up the mapping images --------------- */
1127 /* Note: we only compute map values in tiles that are complete.
1128 * In general, tiles at right and bottom edges will not be
1129 * complete, and we must fill them in later. */
1130 nx = w / sx;
1131 ny = h / sy;
1132 wpls = pixGetWpl(pixs);
1133 datas = pixGetData(pixs);
1134 wplf = pixGetWpl(pixf);
1135 dataf = pixGetData(pixf);
1136 for (i = 0; i < ny; i++) {
1137 lines = datas + sy * i * wpls;
1138 linef = dataf + sy * i * wplf;
1139 for (j = 0; j < nx; j++) {
1140 delx = j * sx;
1141 rsum = gsum = bsum = 0;
1142 count = 0;
1143 for (k = 0; k < sy; k++) {
1144 for (m = 0; m < sx; m++) {
1145 if (GET_DATA_BIT(linef + k * wplf, delx + m) == 0) {
1146 pixel = *(lines + k * wpls + delx + m);
1147 rsum += (pixel >> 24);
1148 gsum += ((pixel >> 16) & 0xff);
1149 bsum += ((pixel >> 8) & 0xff);
1150 count++;
1151 }
1152 }
1153 }
1154 if (count >= mincount) {
1155 rval = rsum / count;
1156 gval = gsum / count;
1157 bval = bsum / count;
1158 pixSetPixel(pixmr, j, i, rval);
1159 pixSetPixel(pixmg, j, i, gval);
1160 pixSetPixel(pixmb, j, i, bval);
1161 }
1162 }
1163 }
1164 pixDestroy(&pixf);
1165
1166 /* If there is an optional mask with fg pixels, erase the previous
1167 * calculation for the corresponding map pixels, setting the
1168 * map values in each of the 3 color maps to 0. Then, when
1169 * all the map holes are filled, these erased pixels will
1170 * be set by the surrounding map values. */
1171 if (pixim) {
1172 wim = pixGetWidth(pixim);
1173 him = pixGetHeight(pixim);
1174 dataim = pixGetData(pixim);
1175 wplim = pixGetWpl(pixim);
1176 for (i = 0; i < ny; i++) {
1177 yim = i * sy + sy / 2;
1178 if (yim >= him)
1179 break;
1180 lineim = dataim + yim * wplim;
1181 for (j = 0; j < nx; j++) {
1182 xim = j * sx + sx / 2;
1183 if (xim >= wim)
1184 break;
1185 if (GET_DATA_BIT(lineim, xim)) {
1186 pixSetPixel(pixmr, j, i, 0);
1187 pixSetPixel(pixmg, j, i, 0);
1188 pixSetPixel(pixmb, j, i, 0);
1189 }
1190 }
1191 }
1192 }
1193
1194 /* ----------------- Now fill in the holes ----------------------- */
1195 if (pixFillMapHoles(pixmr, nx, ny, L_FILL_BLACK) ||
1196 pixFillMapHoles(pixmg, nx, ny, L_FILL_BLACK) ||
1197 pixFillMapHoles(pixmb, nx, ny, L_FILL_BLACK)) {
1198 pixDestroy(&pixmr);
1199 pixDestroy(&pixmg);
1200 pixDestroy(&pixmb);
1201 L_WARNING("can't make the maps\n", __func__);
1202 return 1;
1203 }
1204
1205 /* Finally, for each connected region corresponding to the
1206 * fg mask, reset all pixels to their average value. */
1207 if (pixim && fgpixels) {
1208 scalex = 1. / (l_float32)sx;
1209 scaley = 1. / (l_float32)sy;
1210 pixims = pixScaleBySampling(pixim, scalex, scaley);
1211 pixSmoothConnectedRegions(pixmr, pixims, 2);
1212 pixSmoothConnectedRegions(pixmg, pixims, 2);
1213 pixSmoothConnectedRegions(pixmb, pixims, 2);
1214 pixDestroy(&pixims);
1215 }
1216
1217 *ppixmr = pixmr;
1218 *ppixmg = pixmg;
1219 *ppixmb = pixmb;
1220 pixCopyResolution(*ppixmr, pixs);
1221 pixCopyResolution(*ppixmg, pixs);
1222 pixCopyResolution(*ppixmb, pixs);
1223 return 0;
1224 }
1225
1226
1227 /*!
1228 * \brief pixGetBackgroundGrayMapMorph()
1229 *
1230 * \param[in] pixs 8 bpp grayscale; not cmapped
1231 * \param[in] pixim [optional] 1 bpp 'image' mask; can be null; it
1232 * should not have all foreground pixels
1233 * \param[in] reduction factor at which closing is performed
1234 * \param[in] size of square Sel for the closing; use an odd number
1235 * \param[out] ppixm grayscale map
1236 * \return 0 if OK, 1 on error
1237 */
1238 l_ok
1239 pixGetBackgroundGrayMapMorph(PIX *pixs,
1240 PIX *pixim,
1241 l_int32 reduction,
1242 l_int32 size,
1243 PIX **ppixm)
1244 {
1245 l_int32 nx, ny, empty, fgpixels;
1246 l_float32 scale;
1247 PIX *pixm, *pix1, *pix2, *pix3, *pixims;
1248
1249 if (!ppixm)
1250 return ERROR_INT("&pixm not defined", __func__, 1);
1251 *ppixm = NULL;
1252 if (!pixs || pixGetDepth(pixs) != 8)
1253 return ERROR_INT("pixs not defined or not 8 bpp", __func__, 1);
1254 if (pixGetColormap(pixs))
1255 return ERROR_INT("pixs is colormapped", __func__, 1);
1256 if (pixim && pixGetDepth(pixim) != 1)
1257 return ERROR_INT("pixim not 1 bpp", __func__, 1);
1258
1259 /* Evaluate the mask pixim and make sure it is not all foreground. */
1260 fgpixels = 0; /* boolean for existence of fg mask pixels */
1261 if (pixim) {
1262 pixInvert(pixim, pixim); /* set background pixels to 1 */
1263 pixZero(pixim, &empty);
1264 if (empty)
1265 return ERROR_INT("pixim all fg; no background", __func__, 1);
1266 pixInvert(pixim, pixim); /* revert to original mask */
1267 pixZero(pixim, &empty);
1268 if (!empty) /* there are fg pixels in pixim */
1269 fgpixels = 1;
1270 }
1271
1272 /* Downscale as requested and do the closing to get the background. */
1273 scale = 1. / (l_float32)reduction;
1274 pix1 = pixScaleBySampling(pixs, scale, scale);
1275 pix2 = pixCloseGray(pix1, size, size);
1276 pix3 = pixExtendByReplication(pix2, 1, 1);
1277 pixDestroy(&pix1);
1278 pixDestroy(&pix2);
1279
1280 /* Downscale the image mask, if any, and remove it from the
1281 * background. These pixels will be filled in (twice). */
1282 pixims = NULL;
1283 if (pixim) {
1284 pixims = pixScale(pixim, scale, scale);
1285 pixm = pixConvertTo8(pixims, FALSE);
1286 pixAnd(pixm, pixm, pix3);
1287 }
1288 else
1289 pixm = pixClone(pix3);
1290 pixDestroy(&pix3);
1291
1292 /* Fill all the holes in the map. */
1293 nx = pixGetWidth(pixs) / reduction;
1294 ny = pixGetHeight(pixs) / reduction;
1295 if (pixFillMapHoles(pixm, nx, ny, L_FILL_BLACK)) {
1296 pixDestroy(&pixm);
1297 pixDestroy(&pixims);
1298 L_WARNING("can't make the map\n", __func__);
1299 return 1;
1300 }
1301
1302 /* Finally, for each connected region corresponding to the
1303 * fg mask, reset all pixels to their average value. */
1304 if (pixim && fgpixels)
1305 pixSmoothConnectedRegions(pixm, pixims, 2);
1306 pixDestroy(&pixims);
1307
1308 *ppixm = pixm;
1309 pixCopyResolution(*ppixm, pixs);
1310 return 0;
1311 }
1312
1313
1314 /*!
1315 * \brief pixGetBackgroundRGBMapMorph()
1316 *
1317 * \param[in] pixs 32 bpp rgb
1318 * \param[in] pixim [optional] 1 bpp 'image' mask; can be null; it
1319 * should not have all foreground pixels
1320 * \param[in] reduction factor at which closing is performed
1321 * \param[in] size of square Sel for the closing; use an odd number
1322 * \param[out] ppixmr red component map
1323 * \param[out] ppixmg green component map
1324 * \param[out] ppixmb blue component map
1325 * \return 0 if OK, 1 on error
1326 */
1327 l_ok
1328 pixGetBackgroundRGBMapMorph(PIX *pixs,
1329 PIX *pixim,
1330 l_int32 reduction,
1331 l_int32 size,
1332 PIX **ppixmr,
1333 PIX **ppixmg,
1334 PIX **ppixmb)
1335 {
1336 l_int32 nx, ny, empty, fgpixels;
1337 l_float32 scale;
1338 PIX *pixm, *pixmr, *pixmg, *pixmb, *pix1, *pix2, *pix3, *pixims;
1339
1340 if (!ppixmr || !ppixmg || !ppixmb)
1341 return ERROR_INT("&pixm* not all defined", __func__, 1);
1342 *ppixmr = *ppixmg = *ppixmb = NULL;
1343 if (!pixs)
1344 return ERROR_INT("pixs not defined", __func__, 1);
1345 if (pixGetDepth(pixs) != 32)
1346 return ERROR_INT("pixs not 32 bpp", __func__, 1);
1347 if (pixim && pixGetDepth(pixim) != 1)
1348 return ERROR_INT("pixim not 1 bpp", __func__, 1);
1349
1350 /* Evaluate the mask pixim and make sure it is not all foreground. */
1351 fgpixels = 0; /* boolean for existence of fg mask pixels */
1352 if (pixim) {
1353 pixInvert(pixim, pixim); /* set background pixels to 1 */
1354 pixZero(pixim, &empty);
1355 if (empty)
1356 return ERROR_INT("pixim all fg; no background", __func__, 1);
1357 pixInvert(pixim, pixim); /* revert to original mask */
1358 pixZero(pixim, &empty);
1359 if (!empty) /* there are fg pixels in pixim */
1360 fgpixels = 1;
1361 }
1362
1363 /* Generate an 8 bpp version of the image mask, if it exists */
1364 scale = 1. / (l_float32)reduction;
1365 pixims = NULL;
1366 pixm = NULL;
1367 if (pixim) {
1368 pixims = pixScale(pixim, scale, scale);
1369 pixm = pixConvertTo8(pixims, FALSE);
1370 }
1371
1372 /* Downscale as requested and do the closing to get the background.
1373 * Then remove the image mask pixels from the background. They
1374 * will be filled in (twice) later. Do this for all 3 components. */
1375 pix1 = pixScaleRGBToGrayFast(pixs, reduction, COLOR_RED);
1376 pix2 = pixCloseGray(pix1, size, size);
1377 pix3 = pixExtendByReplication(pix2, 1, 1);
1378 if (pixim)
1379 pixmr = pixAnd(NULL, pixm, pix3);
1380 else
1381 pixmr = pixClone(pix3);
1382 pixDestroy(&pix1);
1383 pixDestroy(&pix2);
1384 pixDestroy(&pix3);
1385
1386 pix1 = pixScaleRGBToGrayFast(pixs, reduction, COLOR_GREEN);
1387 pix2 = pixCloseGray(pix1, size, size);
1388 pix3 = pixExtendByReplication(pix2, 1, 1);
1389 if (pixim)
1390 pixmg = pixAnd(NULL, pixm, pix3);
1391 else
1392 pixmg = pixClone(pix3);
1393 pixDestroy(&pix1);
1394 pixDestroy(&pix2);
1395 pixDestroy(&pix3);
1396
1397 pix1 = pixScaleRGBToGrayFast(pixs, reduction, COLOR_BLUE);
1398 pix2 = pixCloseGray(pix1, size, size);
1399 pix3 = pixExtendByReplication(pix2, 1, 1);
1400 if (pixim)
1401 pixmb = pixAnd(NULL, pixm, pix3);
1402 else
1403 pixmb = pixClone(pix3);
1404 pixDestroy(&pixm);
1405 pixDestroy(&pix1);
1406 pixDestroy(&pix2);
1407 pixDestroy(&pix3);
1408
1409 /* Fill all the holes in the three maps. */
1410 nx = pixGetWidth(pixs) / reduction;
1411 ny = pixGetHeight(pixs) / reduction;
1412 if (pixFillMapHoles(pixmr, nx, ny, L_FILL_BLACK) ||
1413 pixFillMapHoles(pixmg, nx, ny, L_FILL_BLACK) ||
1414 pixFillMapHoles(pixmb, nx, ny, L_FILL_BLACK)) {
1415 pixDestroy(&pixmr);
1416 pixDestroy(&pixmg);
1417 pixDestroy(&pixmb);
1418 pixDestroy(&pixims);
1419 L_WARNING("can't make the maps\n", __func__);
1420 return 1;
1421 }
1422
1423 /* Finally, for each connected region corresponding to the
1424 * fg mask in each component, reset all pixels to their
1425 * average value. */
1426 if (pixim && fgpixels) {
1427 pixSmoothConnectedRegions(pixmr, pixims, 2);
1428 pixSmoothConnectedRegions(pixmg, pixims, 2);
1429 pixSmoothConnectedRegions(pixmb, pixims, 2);
1430 pixDestroy(&pixims);
1431 }
1432
1433 *ppixmr = pixmr;
1434 *ppixmg = pixmg;
1435 *ppixmb = pixmb;
1436 pixCopyResolution(*ppixmr, pixs);
1437 pixCopyResolution(*ppixmg, pixs);
1438 pixCopyResolution(*ppixmb, pixs);
1439 return 0;
1440 }
1441
1442
1443 /*!
1444 * \brief pixFillMapHoles()
1445 *
1446 * \param[in] pix 8 bpp; a map, with one pixel for each tile in
1447 * a larger image
1448 * \param[in] nx number of horizontal pixel tiles that are entirely
1449 * covered with pixels in the original source image
1450 * \param[in] ny ditto for the number of vertical pixel tiles
1451 * \param[in] filltype L_FILL_WHITE or L_FILL_BLACK
1452 * \return 0 if OK, 1 on error
1453 *
1454 * <pre>
1455 * Notes:
1456 * (1) This is an in-place operation on pix (the map). pix is
1457 * typically a low-resolution version of some other image
1458 * from which it was derived, where each pixel in pix
1459 * corresponds to a rectangular tile (say, m x n) of pixels
1460 * in the larger image. All we need to know about the larger
1461 * image is whether or not the rightmost column and bottommost
1462 * row of pixels in pix correspond to tiles that are
1463 * only partially covered by pixels in the larger image.
1464 * (2) Typically, some number of pixels in the input map are
1465 * not known, and their values must be determined by near
1466 * pixels that are known. These unknown pixels are the 'holes'.
1467 * They can take on only two values, 0 and 255, and the
1468 * instruction about which to fill is given by the filltype flag.
1469 * (3) The "holes" can come from two sources. The first is when there
1470 * are not enough foreground or background pixels in a tile;
1471 * the second is when a tile is at least partially covered
1472 * by an image mask. If we're filling holes in a fg mask,
1473 * the holes are initialized to black (0) and use L_FILL_BLACK.
1474 * For filling holes in a bg mask, initialize the holes to
1475 * white (255) and use L_FILL_WHITE.
1476 * (4) If w is the map width, nx = w or nx = w - 1; ditto for h and ny.
1477 * </pre>
1478 */
1479 l_ok
1480 pixFillMapHoles(PIX *pix,
1481 l_int32 nx,
1482 l_int32 ny,
1483 l_int32 filltype)
1484 {
1485 l_int32 w, h, y, nmiss, goodcol, i, j, found, ival, valtest;
1486 l_uint32 val, lastval;
1487 NUMA *na; /* indicates if there is any data in the column */
1488
1489 if (!pix || pixGetDepth(pix) != 8)
1490 return ERROR_INT("pix not defined or not 8 bpp", __func__, 1);
1491 if (pixGetColormap(pix))
1492 return ERROR_INT("pix is colormapped", __func__, 1);
1493
1494 /* ------------- Fill holes in the mapping image columns ----------- */
1495 pixGetDimensions(pix, &w, &h, NULL);
1496 na = numaCreate(0); /* holds flag for which columns have data */
1497 nmiss = 0;
1498 valtest = (filltype == L_FILL_WHITE) ? 255 : 0;
1499 for (j = 0; j < nx; j++) { /* do it by columns */
1500 found = FALSE;
1501 for (i = 0; i < ny; i++) {
1502 pixGetPixel(pix, j, i, &val);
1503 if (val != valtest) {
1504 y = i;
1505 found = TRUE;
1506 break;
1507 }
1508 }
1509 if (found == FALSE) {
1510 numaAddNumber(na, 0); /* no data in the column */
1511 nmiss++;
1512 }
1513 else {
1514 numaAddNumber(na, 1); /* data in the column */
1515 for (i = y - 1; i >= 0; i--) /* replicate upwards to top */
1516 pixSetPixel(pix, j, i, val);
1517 pixGetPixel(pix, j, 0, &lastval);
1518 for (i = 1; i < h; i++) { /* set going down to bottom */
1519 pixGetPixel(pix, j, i, &val);
1520 if (val == valtest)
1521 pixSetPixel(pix, j, i, lastval);
1522 else
1523 lastval = val;
1524 }
1525 }
1526 }
1527
1528 if (nmiss == nx) { /* no data in any column! */
1529 numaDestroy(&na);
1530 L_WARNING("no bg found; no data in any column\n", __func__);
1531 return 1;
1532 }
1533
1534 /* ---------- Fill in missing columns by replication ----------- */
1535 if (nmiss > 0) { /* replicate columns */
1536 /* Find the first good column */
1537 goodcol = 0;
1538 for (j = 0; j < w; j++) {
1539 numaGetIValue(na, j, &ival);
1540 if (ival == 1) {
1541 goodcol = j;
1542 break;
1543 }
1544 }
1545 if (goodcol > 0) { /* copy cols backward */
1546 for (j = goodcol - 1; j >= 0; j--)
1547 pixRasterop(pix, j, 0, 1, h, PIX_SRC, pix, j + 1, 0);
1548 }
1549 for (j = goodcol + 1; j < w; j++) { /* copy cols forward */
1550 numaGetIValue(na, j, &ival);
1551 if (ival == 0) {
1552 /* Copy the column to the left of j */
1553 pixRasterop(pix, j, 0, 1, h, PIX_SRC, pix, j - 1, 0);
1554 }
1555 }
1556 }
1557 if (w > nx) { /* replicate the last column */
1558 pixRasterop(pix, w - 1, 0, 1, h, PIX_SRC, pix, w - 2, 0);
1559 }
1560
1561 numaDestroy(&na);
1562 return 0;
1563 }
1564
1565
1566 /*!
1567 * \brief pixExtendByReplication()
1568 *
1569 * \param[in] pixs 8 bpp
1570 * \param[in] addw number of extra pixels horizontally to add
1571 * \param[in] addh number of extra pixels vertically to add
1572 * \return pixd extended with replicated pixel values, or NULL on error
1573 *
1574 * <pre>
1575 * Notes:
1576 * (1) The pixel values are extended to the left and down, as required.
1577 * </pre>
1578 */
1579 PIX *
1580 pixExtendByReplication(PIX *pixs,
1581 l_int32 addw,
1582 l_int32 addh)
1583 {
1584 l_int32 w, h, i, j;
1585 l_uint32 val;
1586 PIX *pixd;
1587
1588 if (!pixs || pixGetDepth(pixs) != 8)
1589 return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, NULL);
1590
1591 if (addw == 0 && addh == 0)
1592 return pixCopy(NULL, pixs);
1593
1594 pixGetDimensions(pixs, &w, &h, NULL);
1595 if ((pixd = pixCreate(w + addw, h + addh, 8)) == NULL)
1596 return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
1597 pixRasterop(pixd, 0, 0, w, h, PIX_SRC, pixs, 0, 0);
1598
1599 if (addw > 0) {
1600 for (i = 0; i < h; i++) {
1601 pixGetPixel(pixd, w - 1, i, &val);
1602 for (j = 0; j < addw; j++)
1603 pixSetPixel(pixd, w + j, i, val);
1604 }
1605 }
1606
1607 if (addh > 0) {
1608 for (j = 0; j < w + addw; j++) {
1609 pixGetPixel(pixd, j, h - 1, &val);
1610 for (i = 0; i < addh; i++)
1611 pixSetPixel(pixd, j, h + i, val);
1612 }
1613 }
1614
1615 pixCopyResolution(pixd, pixs);
1616 return pixd;
1617 }
1618
1619
1620 /*!
1621 * \brief pixSmoothConnectedRegions()
1622 *
1623 * \param[in] pixs 8 bpp grayscale; no colormap
1624 * \param[in] pixm [optional] 1 bpp; if null, this is a no-op
1625 * \param[in] factor subsampling factor for getting average; >= 1
1626 * \return 0 if OK, 1 on error
1627 *
1628 * <pre>
1629 * Notes:
1630 * (1) The pixels in pixs corresponding to those in each
1631 * 8-connected region in the mask are set to the average value.
1632 * (2) This is required for adaptive mapping to avoid the
1633 * generation of stripes in the background map, due to
1634 * variations in the pixel values near the edges of mask regions.
1635 * (3) This function is optimized for background smoothing, where
1636 * there are a relatively small number of components. It will
1637 * be inefficient if used where there are many small components.
1638 * </pre>
1639 */
1640 l_ok
1641 pixSmoothConnectedRegions(PIX *pixs,
1642 PIX *pixm,
1643 l_int32 factor)
1644 {
1645 l_int32 empty, i, n, x, y;
1646 l_float32 aveval;
1647 BOXA *boxa;
1648 PIX *pixmc;
1649 PIXA *pixa;
1650
1651 if (!pixs || pixGetDepth(pixs) != 8)
1652 return ERROR_INT("pixs not defined or not 8 bpp", __func__, 1);
1653 if (pixGetColormap(pixs))
1654 return ERROR_INT("pixs has colormap", __func__, 1);
1655 if (!pixm) {
1656 L_INFO("pixm not defined\n", __func__);
1657 return 0;
1658 }
1659 if (pixGetDepth(pixm) != 1)
1660 return ERROR_INT("pixm not 1 bpp", __func__, 1);
1661 pixZero(pixm, &empty);
1662 if (empty) {
1663 L_INFO("pixm has no fg pixels; nothing to do\n", __func__);
1664 return 0;
1665 }
1666
1667 boxa = pixConnComp(pixm, &pixa, 8);
1668 n = boxaGetCount(boxa);
1669 for (i = 0; i < n; i++) {
1670 if ((pixmc = pixaGetPix(pixa, i, L_CLONE)) == NULL) {
1671 L_WARNING("missing pixmc!\n", __func__);
1672 continue;
1673 }
1674 boxaGetBoxGeometry(boxa, i, &x, &y, NULL, NULL);
1675 pixGetAverageMasked(pixs, pixmc, x, y, factor, L_MEAN_ABSVAL, &aveval);
1676 pixPaintThroughMask(pixs, pixmc, x, y, (l_int32)aveval);
1677 pixDestroy(&pixmc);
1678 }
1679
1680 boxaDestroy(&boxa);
1681 pixaDestroy(&pixa);
1682 return 0;
1683 }
1684
1685
1686 /*------------------------------------------------------------------*
1687 * Measurement of local foreground *
1688 *------------------------------------------------------------------*/
1689 #if 0 /* Not working properly: do not use */
1690
1691 /*!
1692 * \brief pixGetForegroundGrayMap()
1693 *
1694 * \param[in] pixs 8 bpp
1695 * \param[in] pixim [optional] 1 bpp 'image' mask; can be null
1696 * \param[in] sx, sy src tile size, in pixels
1697 * \param[in] thresh threshold for determining foreground
1698 * \param[out] ppixd 8 bpp grayscale map
1699 * \return 0 if OK, 1 on error
1700 *
1701 * <pre>
1702 * Notes:
1703 * (1) Each (sx, sy) tile of pixs gets mapped to one pixel in pixd.
1704 * (2) pixd is the estimate of the fg (darkest) value within each tile.
1705 * (3) All pixels in pixd that are in 'image' regions, as specified
1706 * by pixim, are given the background value 0.
1707 * (4) For pixels in pixd that can't directly be given a fg value,
1708 * the value is inferred by propagating from neighboring pixels.
1709 * (5) In practice, pixd can be used to normalize the fg, and
1710 * it can be done after background normalization.
1711 * (6) The overall procedure is:
1712 * ~ reduce 2x by sampling
1713 * ~ paint all 'image' pixels white, so that they don't
1714 * ~ participate in the Min reduction
1715 * ~ do a further (sx, sy) Min reduction -- think of
1716 * it as a large opening followed by subsampling by the
1717 * reduction factors
1718 * ~ threshold the result to identify fg, and set the
1719 * bg pixels to 255 (these are 'holes')
1720 * ~ fill holes by propagation from fg values
1721 * ~ replicatively expand by 2x, arriving at the final
1722 * resolution of pixd
1723 * ~ smooth with a 17x17 kernel
1724 * ~ paint the 'image' regions black
1725 * </pre>
1726 */
1727 l_ok
1728 pixGetForegroundGrayMap(PIX *pixs,
1729 PIX *pixim,
1730 l_int32 sx,
1731 l_int32 sy,
1732 l_int32 thresh,
1733 PIX **ppixd)
1734 {
1735 l_int32 w, h, d, wd, hd;
1736 l_int32 empty, fgpixels;
1737 PIX *pixd, *piximi, *pixim2, *pixims, *pixs2, *pixb, *pixt1, *pixt2, *pixt3;
1738
1739 if (!ppixd)
1740 return ERROR_INT("&pixd not defined", __func__, 1);
1741 *ppixd = NULL;
1742 if (!pixs)
1743 return ERROR_INT("pixs not defined", __func__, 1);
1744 pixGetDimensions(pixs, &w, &h, &d);
1745 if (d != 8)
1746 return ERROR_INT("pixs not 8 bpp", __func__, 1);
1747 if (pixim && pixGetDepth(pixim) != 1)
1748 return ERROR_INT("pixim not 1 bpp", __func__, 1);
1749 if (sx < 2 || sy < 2)
1750 return ERROR_INT("sx and sy must be >= 2", __func__, 1);
1751
1752 /* Generate pixd, which is reduced by the factors (sx, sy). */
1753 wd = (w + sx - 1) / sx;
1754 hd = (h + sy - 1) / sy;
1755 pixd = pixCreate(wd, hd, 8);
1756 *ppixd = pixd;
1757
1758 /* Evaluate the 'image' mask, pixim. If it is all fg,
1759 * the output pixd has all pixels with value 0. */
1760 fgpixels = 0; /* boolean for existence of fg pixels in the image mask. */
1761 if (pixim) {
1762 piximi = pixInvert(NULL, pixim); /* set non-image pixels to 1 */
1763 pixZero(piximi, &empty);
1764 pixDestroy(&piximi);
1765 if (empty) /* all 'image'; return with all pixels set to 0 */
1766 return 0;
1767 pixZero(pixim, &empty);
1768 if (!empty) /* there are fg pixels in pixim */
1769 fgpixels = 1;
1770 }
1771
1772 /* 2x subsampling; paint white through 'image' mask. */
1773 pixs2 = pixScaleBySampling(pixs, 0.5, 0.5);
1774 if (pixim && fgpixels) {
1775 pixim2 = pixReduceBinary2(pixim, NULL);
1776 pixPaintThroughMask(pixs2, pixim2, 0, 0, 255);
1777 pixDestroy(&pixim2);
1778 }
1779
1780 /* Min (erosion) downscaling; total reduction (4 sx, 4 sy). */
1781 pixt1 = pixScaleGrayMinMax(pixs2, sx, sy, L_CHOOSE_MIN);
1782
1783 /* pixDisplay(pixt1, 300, 200); */
1784
1785 /* Threshold to identify fg; paint bg pixels to white. */
1786 pixb = pixThresholdToBinary(pixt1, thresh); /* fg pixels */
1787 pixInvert(pixb, pixb);
1788 pixPaintThroughMask(pixt1, pixb, 0, 0, 255);
1789 pixDestroy(&pixb);
1790
1791 /* Replicative expansion by 2x to (sx, sy). */
1792 pixt2 = pixExpandReplicate(pixt1, 2);
1793
1794 /* pixDisplay(pixt2, 500, 200); */
1795
1796 /* Fill holes in the fg by propagation */
1797 pixFillMapHoles(pixt2, w / sx, h / sy, L_FILL_WHITE);
1798
1799 /* pixDisplay(pixt2, 700, 200); */
1800
1801 /* Smooth with 17x17 kernel. */
1802 pixt3 = pixBlockconv(pixt2, 8, 8);
1803 pixRasterop(pixd, 0, 0, wd, hd, PIX_SRC, pixt3, 0, 0);
1804
1805 /* Paint the image parts black. */
1806 pixims = pixScaleBySampling(pixim, 1. / sx, 1. / sy);
1807 pixPaintThroughMask(pixd, pixims, 0, 0, 0);
1808
1809 pixDestroy(&pixs2);
1810 pixDestroy(&pixt1);
1811 pixDestroy(&pixt2);
1812 pixDestroy(&pixt3);
1813 return 0;
1814 }
1815 #endif /* Not working properly: do not use */
1816
1817
1818 /*------------------------------------------------------------------*
1819 * Generate inverted background map *
1820 *------------------------------------------------------------------*/
1821 /*!
1822 * \brief pixGetInvBackgroundMap()
1823 *
1824 * \param[in] pixs 8 bpp grayscale; no colormap
1825 * \param[in] bgval target bg val; typ. > 128
1826 * \param[in] smoothx half-width of block convolution kernel width
1827 * \param[in] smoothy half-width of block convolution kernel height
1828 * \return pixd 16 bpp, or NULL on error
1829 *
1830 * <pre>
1831 * Notes:
1832 * (1) bgval should typically be > 120 and < 240
1833 * (2) pixd is a normalization image; the original image is
1834 * multiplied by pixd and the result is divided by 256.
1835 * </pre>
1836 */
1837 PIX *
1838 pixGetInvBackgroundMap(PIX *pixs,
1839 l_int32 bgval,
1840 l_int32 smoothx,
1841 l_int32 smoothy)
1842 {
1843 l_int32 w, h, wplsm, wpld, i, j;
1844 l_int32 val, val16;
1845 l_uint32 *datasm, *datad, *linesm, *lined;
1846 PIX *pixsm, *pixd;
1847
1848 if (!pixs || pixGetDepth(pixs) != 8)
1849 return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, NULL);
1850 if (pixGetColormap(pixs))
1851 return (PIX *)ERROR_PTR("pixs has colormap", __func__, NULL);
1852 pixGetDimensions(pixs, &w, &h, NULL);
1853 if (w < 5 || h < 5)
1854 return (PIX *)ERROR_PTR("w and h must be >= 5", __func__, NULL);
1855
1856 /* smooth the map image */
1857 pixsm = pixBlockconv(pixs, smoothx, smoothy);
1858 datasm = pixGetData(pixsm);
1859 wplsm = pixGetWpl(pixsm);
1860
1861 /* invert the map image, scaling up to preserve dynamic range */
1862 pixd = pixCreate(w, h, 16);
1863 datad = pixGetData(pixd);
1864 wpld = pixGetWpl(pixd);
1865 for (i = 0; i < h; i++) {
1866 linesm = datasm + i * wplsm;
1867 lined = datad + i * wpld;
1868 for (j = 0; j < w; j++) {
1869 val = GET_DATA_BYTE(linesm, j);
1870 if (val > 0)
1871 val16 = (256 * bgval) / val;
1872 else { /* shouldn't happen */
1873 L_WARNING("smoothed bg has 0 pixel!\n", __func__);
1874 val16 = bgval / 2;
1875 }
1876 SET_DATA_TWO_BYTES(lined, j, val16);
1877 }
1878 }
1879
1880 pixDestroy(&pixsm);
1881 pixCopyResolution(pixd, pixs);
1882 return pixd;
1883 }
1884
1885
1886 /*------------------------------------------------------------------*
1887 * Apply background map to image *
1888 *------------------------------------------------------------------*/
1889 /*!
1890 * \brief pixApplyInvBackgroundGrayMap()
1891 *
1892 * \param[in] pixs 8 bpp grayscale; no colormap
1893 * \param[in] pixm 16 bpp, inverse background map
1894 * \param[in] sx tile width in pixels
1895 * \param[in] sy tile height in pixels
1896 * \return pixd 8 bpp, or NULL on error
1897 */
1898 PIX *
1899 pixApplyInvBackgroundGrayMap(PIX *pixs,
1900 PIX *pixm,
1901 l_int32 sx,
1902 l_int32 sy)
1903 {
1904 l_int32 w, h, wm, hm, wpls, wpld, i, j, k, m, xoff, yoff;
1905 l_int32 vals, vald;
1906 l_uint32 val16;
1907 l_uint32 *datas, *datad, *lines, *lined, *flines, *flined;
1908 PIX *pixd;
1909
1910 if (!pixs || pixGetDepth(pixs) != 8)
1911 return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, NULL);
1912 if (pixGetColormap(pixs))
1913 return (PIX *)ERROR_PTR("pixs has colormap", __func__, NULL);
1914 if (!pixm || pixGetDepth(pixm) != 16)
1915 return (PIX *)ERROR_PTR("pixm undefined or not 16 bpp", __func__, NULL);
1916 if (sx == 0 || sy == 0)
1917 return (PIX *)ERROR_PTR("invalid sx and/or sy", __func__, NULL);
1918
1919 datas = pixGetData(pixs);
1920 wpls = pixGetWpl(pixs);
1921 pixGetDimensions(pixs, &w, &h, NULL);
1922 pixGetDimensions(pixm, &wm, &hm, NULL);
1923 if ((pixd = pixCreateTemplate(pixs)) == NULL)
1924 return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
1925 datad = pixGetData(pixd);
1926 wpld = pixGetWpl(pixd);
1927 for (i = 0; i < hm; i++) {
1928 lines = datas + sy * i * wpls;
1929 lined = datad + sy * i * wpld;
1930 yoff = sy * i;
1931 for (j = 0; j < wm; j++) {
1932 pixGetPixel(pixm, j, i, &val16);
1933 xoff = sx * j;
1934 for (k = 0; k < sy && yoff + k < h; k++) {
1935 flines = lines + k * wpls;
1936 flined = lined + k * wpld;
1937 for (m = 0; m < sx && xoff + m < w; m++) {
1938 vals = GET_DATA_BYTE(flines, xoff + m);
1939 vald = (vals * val16) / 256;
1940 vald = L_MIN(vald, 255);
1941 SET_DATA_BYTE(flined, xoff + m, vald);
1942 }
1943 }
1944 }
1945 }
1946
1947 return pixd;
1948 }
1949
1950
1951 /*!
1952 * \brief pixApplyInvBackgroundRGBMap()
1953 *
1954 * \param[in] pixs 32 bpp rbg
1955 * \param[in] pixmr 16 bpp, red inverse background map
1956 * \param[in] pixmg 16 bpp, green inverse background map
1957 * \param[in] pixmb 16 bpp, blue inverse background map
1958 * \param[in] sx tile width in pixels
1959 * \param[in] sy tile height in pixels
1960 * \return pixd 32 bpp rbg, or NULL on error
1961 */
1962 PIX *
1963 pixApplyInvBackgroundRGBMap(PIX *pixs,
1964 PIX *pixmr,
1965 PIX *pixmg,
1966 PIX *pixmb,
1967 l_int32 sx,
1968 l_int32 sy)
1969 {
1970 l_int32 w, h, wm, hm, wpls, wpld, i, j, k, m, xoff, yoff;
1971 l_int32 rvald, gvald, bvald;
1972 l_uint32 vals;
1973 l_uint32 rval16, gval16, bval16;
1974 l_uint32 *datas, *datad, *lines, *lined, *flines, *flined;
1975 PIX *pixd;
1976
1977 if (!pixs)
1978 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
1979 if (pixGetDepth(pixs) != 32)
1980 return (PIX *)ERROR_PTR("pixs not 32 bpp", __func__, NULL);
1981 if (!pixmr || !pixmg || !pixmb)
1982 return (PIX *)ERROR_PTR("pix maps not all defined", __func__, NULL);
1983 if (pixGetDepth(pixmr) != 16 || pixGetDepth(pixmg) != 16 ||
1984 pixGetDepth(pixmb) != 16)
1985 return (PIX *)ERROR_PTR("pix maps not all 16 bpp", __func__, NULL);
1986 if (sx == 0 || sy == 0)
1987 return (PIX *)ERROR_PTR("invalid sx and/or sy", __func__, NULL);
1988
1989 datas = pixGetData(pixs);
1990 wpls = pixGetWpl(pixs);
1991 w = pixGetWidth(pixs);
1992 h = pixGetHeight(pixs);
1993 wm = pixGetWidth(pixmr);
1994 hm = pixGetHeight(pixmr);
1995 if ((pixd = pixCreateTemplate(pixs)) == NULL)
1996 return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
1997 datad = pixGetData(pixd);
1998 wpld = pixGetWpl(pixd);
1999 for (i = 0; i < hm; i++) {
2000 lines = datas + sy * i * wpls;
2001 lined = datad + sy * i * wpld;
2002 yoff = sy * i;
2003 for (j = 0; j < wm; j++) {
2004 pixGetPixel(pixmr, j, i, &rval16);
2005 pixGetPixel(pixmg, j, i, &gval16);
2006 pixGetPixel(pixmb, j, i, &bval16);
2007 xoff = sx * j;
2008 for (k = 0; k < sy && yoff + k < h; k++) {
2009 flines = lines + k * wpls;
2010 flined = lined + k * wpld;
2011 for (m = 0; m < sx && xoff + m < w; m++) {
2012 vals = *(flines + xoff + m);
2013 rvald = ((vals >> 24) * rval16) / 256;
2014 rvald = L_MIN(rvald, 255);
2015 gvald = (((vals >> 16) & 0xff) * gval16) / 256;
2016 gvald = L_MIN(gvald, 255);
2017 bvald = (((vals >> 8) & 0xff) * bval16) / 256;
2018 bvald = L_MIN(bvald, 255);
2019 composeRGBPixel(rvald, gvald, bvald, flined + xoff + m);
2020 }
2021 }
2022 }
2023 }
2024
2025 return pixd;
2026 }
2027
2028
2029 /*------------------------------------------------------------------*
2030 * Apply variable map *
2031 *------------------------------------------------------------------*/
2032 /*!
2033 * \brief pixApplyVariableGrayMap()
2034 *
2035 * \param[in] pixs 8 bpp
2036 * \param[in] pixg 8 bpp, variable map
2037 * \param[in] target typ. 128 for threshold
2038 * \return pixd 8 bpp, or NULL on error
2039 *
2040 * <pre>
2041 * Notes:
2042 * (1) Suppose you have an image that you want to transform based
2043 * on some photometric measurement at each point, such as the
2044 * threshold value for binarization. Representing the photometric
2045 * measurement as an image pixg, you can threshold in input image
2046 * using pixVarThresholdToBinary(). Alternatively, you can map
2047 * the input image pointwise so that the threshold over the
2048 * entire image becomes a constant, such as 128. For example,
2049 * if a pixel in pixg is 150 and the target is 128, the
2050 * corresponding pixel in pixs is mapped linearly to a value
2051 * (128/150) of the input value. If the resulting mapped image
2052 * pixd were then thresholded at 128, you would obtain the
2053 * same result as a direct binarization using pixg with
2054 * pixVarThresholdToBinary().
2055 * (2) The sizes of pixs and pixg must be equal.
2056 * </pre>
2057 */
2058 PIX *
2059 pixApplyVariableGrayMap(PIX *pixs,
2060 PIX *pixg,
2061 l_int32 target)
2062 {
2063 l_int32 i, j, w, h, d, wpls, wplg, wpld, vals, valg, vald;
2064 l_uint8 *lut;
2065 l_uint32 *datas, *datag, *datad, *lines, *lineg, *lined;
2066 l_float32 fval;
2067 PIX *pixd;
2068
2069 if (!pixs)
2070 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
2071 if (!pixg)
2072 return (PIX *)ERROR_PTR("pixg not defined", __func__, NULL);
2073 if (!pixSizesEqual(pixs, pixg))
2074 return (PIX *)ERROR_PTR("pix sizes not equal", __func__, NULL);
2075 pixGetDimensions(pixs, &w, &h, &d);
2076 if (d != 8)
2077 return (PIX *)ERROR_PTR("depth not 8 bpp", __func__, NULL);
2078
2079 /* Generate a LUT for the mapping if the image is large enough
2080 * to warrant the overhead. The LUT is of size 2^16. For the
2081 * index to the table, get the MSB from pixs and the LSB from pixg.
2082 * Note: this LUT is bigger than the typical 32K L1 cache, so
2083 * we expect cache misses. L2 latencies are about 5ns. But
2084 * division is slooooow. For large images, this function is about
2085 * 4x faster when using the LUT. C'est la vie. */
2086 lut = NULL;
2087 if (w * h > 100000) { /* more pixels than 2^16 */
2088 lut = (l_uint8 *)LEPT_CALLOC(0x10000, sizeof(l_uint8));
2089 for (i = 0; i < 256; i++) {
2090 for (j = 0; j < 256; j++) {
2091 fval = (l_float32)(i * target) / (j + 0.5);
2092 lut[(i << 8) + j] = L_MIN(255, (l_int32)(fval + 0.5));
2093 }
2094 }
2095 }
2096
2097 if ((pixd = pixCreate(w, h, 8)) == NULL) {
2098 LEPT_FREE(lut);
2099 return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
2100 }
2101 pixCopyResolution(pixd, pixs);
2102 datad = pixGetData(pixd);
2103 wpld = pixGetWpl(pixd);
2104 datas = pixGetData(pixs);
2105 wpls = pixGetWpl(pixs);
2106 datag = pixGetData(pixg);
2107 wplg = pixGetWpl(pixg);
2108 for (i = 0; i < h; i++) {
2109 lines = datas + i * wpls;
2110 lineg = datag + i * wplg;
2111 lined = datad + i * wpld;
2112 if (lut) {
2113 for (j = 0; j < w; j++) {
2114 vals = GET_DATA_BYTE(lines, j);
2115 valg = GET_DATA_BYTE(lineg, j);
2116 vald = lut[(vals << 8) + valg];
2117 SET_DATA_BYTE(lined, j, vald);
2118 }
2119 }
2120 else {
2121 for (j = 0; j < w; j++) {
2122 vals = GET_DATA_BYTE(lines, j);
2123 valg = GET_DATA_BYTE(lineg, j);
2124 fval = (l_float32)(vals * target) / (valg + 0.5);
2125 vald = L_MIN(255, (l_int32)(fval + 0.5));
2126 SET_DATA_BYTE(lined, j, vald);
2127 }
2128 }
2129 }
2130
2131 LEPT_FREE(lut);
2132 return pixd;
2133 }
2134
2135
2136 /*------------------------------------------------------------------*
2137 * Non-adaptive (global) mapping *
2138 *------------------------------------------------------------------*/
2139 /*!
2140 * \brief pixGlobalNormRGB()
2141 *
2142 * \param[in] pixd [optional] null, existing or equal to pixs
2143 * \param[in] pixs 32 bpp rgb, or colormapped
2144 * \param[in] rval, gval, bval pixel values in pixs that are
2145 * linearly mapped to mapval
2146 * \param[in] mapval use 255 for mapping to white
2147 * \return pixd 32 bpp rgb or colormapped, or NULL on error
2148 *
2149 * <pre>
2150 * Notes:
2151 * (1) The value of pixd determines if the results are written to a
2152 * new pix (use NULL), in-place to pixs (use pixs), or to some
2153 * other existing pix.
2154 * (2) This does a global normalization of an image where the
2155 * r,g,b color components are not balanced. Thus, white in pixs is
2156 * represented by a set of r,g,b values that are not all 255.
2157 * (3) The input values (rval, gval, bval) should be chosen to
2158 * represent the gray color (mapval, mapval, mapval) in src.
2159 * Thus, this function will map (rval, gval, bval) to that gray color.
2160 * (4) Typically, mapval = 255, so that (rval, gval, bval)
2161 * corresponds to the white point of src. In that case, these
2162 * parameters should be chosen so that few pixels have higher values.
2163 * (5) In all cases, we do a linear TRC separately on each of the
2164 * components, saturating at 255.
2165 * (6) If the input pix is 8 bpp without a colormap, you can get
2166 * this functionality with mapval = 255 by calling:
2167 * pixGammaTRC(pixd, pixs, 1.0, 0, bgval);
2168 * where bgval is the value you want to be mapped to 255.
2169 * Or more generally, if you want bgval to be mapped to mapval:
2170 * pixGammaTRC(pixd, pixs, 1.0, 0, 255 * bgval / mapval);
2171 * </pre>
2172 */
2173 PIX *
2174 pixGlobalNormRGB(PIX *pixd,
2175 PIX *pixs,
2176 l_int32 rval,
2177 l_int32 gval,
2178 l_int32 bval,
2179 l_int32 mapval)
2180 {
2181 l_int32 w, h, d, i, j, ncolors, rv, gv, bv, wpl;
2182 l_int32 *rarray, *garray, *barray;
2183 l_uint32 *data, *line;
2184 NUMA *nar, *nag, *nab;
2185 PIXCMAP *cmap;
2186
2187 if (!pixs)
2188 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
2189 cmap = pixGetColormap(pixs);
2190 pixGetDimensions(pixs, &w, &h, &d);
2191 if (!cmap && d != 32)
2192 return (PIX *)ERROR_PTR("pixs not cmapped or 32 bpp", __func__, NULL);
2193 if (mapval <= 0) {
2194 L_WARNING("mapval must be > 0; setting to 255\n", __func__);
2195 mapval = 255;
2196 }
2197
2198 /* Prepare pixd to be a copy of pixs */
2199 if ((pixd = pixCopy(pixd, pixs)) == NULL)
2200 return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
2201
2202 /* Generate the TRC maps for each component. Make sure the
2203 * upper range for each color is greater than zero. */
2204 nar = numaGammaTRC(1.0, 0, L_MAX(1, 255 * rval / mapval));
2205 nag = numaGammaTRC(1.0, 0, L_MAX(1, 255 * gval / mapval));
2206 nab = numaGammaTRC(1.0, 0, L_MAX(1, 255 * bval / mapval));
2207
2208 /* Extract copies of the internal arrays */
2209 rarray = numaGetIArray(nar);
2210 garray = numaGetIArray(nag);
2211 barray = numaGetIArray(nab);
2212 if (!nar || !nag || !nab || !rarray || !garray || !barray) {
2213 L_ERROR("allocation failure in arrays\n", __func__);
2214 goto cleanup_arrays;
2215 }
2216
2217 if (cmap) {
2218 ncolors = pixcmapGetCount(cmap);
2219 for (i = 0; i < ncolors; i++) {
2220 pixcmapGetColor(cmap, i, &rv, &gv, &bv);
2221 pixcmapResetColor(cmap, i, rarray[rv], garray[gv], barray[bv]);
2222 }
2223 }
2224 else {
2225 data = pixGetData(pixd);
2226 wpl = pixGetWpl(pixd);
2227 for (i = 0; i < h; i++) {
2228 line = data + i * wpl;
2229 for (j = 0; j < w; j++) {
2230 extractRGBValues(line[j], &rv, &gv, &bv);
2231 composeRGBPixel(rarray[rv], garray[gv], barray[bv], line + j);
2232 }
2233 }
2234 }
2235
2236 cleanup_arrays:
2237 numaDestroy(&nar);
2238 numaDestroy(&nag);
2239 numaDestroy(&nab);
2240 LEPT_FREE(rarray);
2241 LEPT_FREE(garray);
2242 LEPT_FREE(barray);
2243 return pixd;
2244 }
2245
2246
2247 /*!
2248 * \brief pixGlobalNormNoSatRGB()
2249 *
2250 * \param[in] pixd [optional] null, existing or equal to pixs
2251 * \param[in] pixs 32 bpp rgb
2252 * \param[in] rval, gval, bval pixel values in pixs that are
2253 * linearly mapped to mapval; but see below
2254 * \param[in] factor subsampling factor; integer >= 1
2255 * \param[in] rank between 0.0 and 1.0; typ. use a value near 1.0
2256 * \return pixd 32 bpp rgb, or NULL on error
2257 *
2258 * <pre>
2259 * Notes:
2260 * (1) This is a version of pixGlobalNormRGB(), where the output
2261 * intensity is scaled back so that a controlled fraction of
2262 * pixel components is allowed to saturate. See comments in
2263 * pixGlobalNormRGB().
2264 * (2) The value of pixd determines if the results are written to a
2265 * new pix (use NULL), in-place to pixs (use pixs), or to some
2266 * other existing pix.
2267 * (3) This does a global normalization of an image where the
2268 * r,g,b color components are not balanced. Thus, white in pixs is
2269 * represented by a set of r,g,b values that are not all 255.
2270 * (4) The input values (rval, gval, bval) can be chosen to be the
2271 * color that, after normalization, becomes white background.
2272 * For images that are mostly background, the closer these values
2273 * are to the median component values, the closer the resulting
2274 * background will be to gray, becoming white at the brightest places.
2275 * (5) The mapval used in pixGlobalNormRGB() is computed here to
2276 * avoid saturation of any component in the image (save for a
2277 * fraction of the pixels given by the input rank value).
2278 * </pre>
2279 */
2280 PIX *
2281 pixGlobalNormNoSatRGB(PIX *pixd,
2282 PIX *pixs,
2283 l_int32 rval,
2284 l_int32 gval,
2285 l_int32 bval,
2286 l_int32 factor,
2287 l_float32 rank)
2288 {
2289 l_int32 mapval;
2290 l_float32 rankrval, rankgval, rankbval;
2291 l_float32 rfract, gfract, bfract, maxfract;
2292
2293 if (!pixs)
2294 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
2295 if (pixGetDepth(pixs) != 32)
2296 return (PIX *)ERROR_PTR("pixs not 32 bpp", __func__, NULL);
2297 if (factor < 1)
2298 return (PIX *)ERROR_PTR("sampling factor < 1", __func__, NULL);
2299 if (rank < 0.0 || rank > 1.0)
2300 return (PIX *)ERROR_PTR("rank not in [0.0 ... 1.0]", __func__, NULL);
2301 if (rval <= 0 || gval <= 0 || bval <= 0)
2302 return (PIX *)ERROR_PTR("invalid estim. color values", __func__, NULL);
2303
2304 /* The max value for each component may be larger than the
2305 * input estimated background value. In that case, mapping
2306 * for those pixels would saturate. To prevent saturation,
2307 * we compute the fraction for each component by which we
2308 * would oversaturate. Then take the max of these, and
2309 * reduce, uniformly over all components, the output intensity
2310 * by this value. Then no component will saturate.
2311 * In practice, if rank < 1.0, a fraction of pixels
2312 * may have a component saturate. By keeping rank close to 1.0,
2313 * that fraction can be made arbitrarily small. */
2314 pixGetRankValueMaskedRGB(pixs, NULL, 0, 0, factor, rank, &rankrval,
2315 &rankgval, &rankbval);
2316 rfract = rankrval / (l_float32)rval;
2317 gfract = rankgval / (l_float32)gval;
2318 bfract = rankbval / (l_float32)bval;
2319 maxfract = L_MAX(rfract, gfract);
2320 maxfract = L_MAX(maxfract, bfract);
2321 #if DEBUG_GLOBAL
2322 lept_stderr("rankrval = %7.2f, rankgval = %7.2f, rankbval = %7.2f\n",
2323 rankrval, rankgval, rankbval);
2324 lept_stderr("rfract = %7.4f, gfract = %7.4f, bfract = %7.4f\n",
2325 rfract, gfract, bfract);
2326 #endif /* DEBUG_GLOBAL */
2327
2328 mapval = (l_int32)(255. / maxfract);
2329 pixd = pixGlobalNormRGB(pixd, pixs, rval, gval, bval, mapval);
2330 return pixd;
2331 }
2332
2333
2334 /*------------------------------------------------------------------*
2335 * Adaptive threshold spread normalization *
2336 *------------------------------------------------------------------*/
2337 /*!
2338 * \brief pixThresholdSpreadNorm()
2339 *
2340 * \param[in] pixs 8 bpp grayscale; not colormapped
2341 * \param[in] filtertype L_SOBEL_EDGE or L_TWO_SIDED_EDGE;
2342 * \param[in] edgethresh threshold on magnitude of edge filter;
2343 * typ 10-20
2344 * \param[in] smoothx, smoothy half-width of convolution kernel applied to
2345 * spread threshold: use 0 for no smoothing
2346 * \param[in] gamma gamma correction; typ. about 0.7
2347 * \param[in] minval input value that gives 0 for output; typ. -25
2348 * \param[in] maxval input value that gives 255 for output;
2349 * typ. 255
2350 * \param[in] targetthresh target threshold for normalization
2351 * \param[out] ppixth [optional] computed local threshold value
2352 * \param[out] ppixb [optional] thresholded normalized image
2353 * \param[out] ppixd [optional] normalized image
2354 * \return 0 if OK, 1 on error
2355 *
2356 * <pre>
2357 * Notes:
2358 * (1) The basis of this approach is the use of seed spreading
2359 * on a (possibly) sparse set of estimates for the local threshold.
2360 * The resulting dense estimates are smoothed by convolution
2361 * and used to either threshold the input image or normalize it
2362 * with a local transformation that linearly maps the pixels so
2363 * that the local threshold estimate becomes constant over the
2364 * resulting image. This approach is one of several that
2365 * have been suggested (and implemented) by Ray Smith.
2366 * (2) You can use either the Sobel or TwoSided edge filters.
2367 * The results appear to be similar, using typical values
2368 * of edgethresh in the rang 10-20.
2369 * (3) To skip the trc enhancement, use gamma = 1.0, minval = 0
2370 * and maxval = 255.
2371 * (4) For the normalized image pixd, each pixel is linearly mapped
2372 * in such a way that the local threshold is equal to targetthresh.
2373 * (5) The full width and height of the convolution kernel
2374 * are (2 * smoothx + 1) and (2 * smoothy + 1).
2375 * (6) This function can be used with the pixtiling utility if the
2376 * images are too large. See pixOtsuAdaptiveThreshold() for
2377 * an example of this.
2378 * </pre>
2379 */
2380 l_ok
2381 pixThresholdSpreadNorm(PIX *pixs,
2382 l_int32 filtertype,
2383 l_int32 edgethresh,
2384 l_int32 smoothx,
2385 l_int32 smoothy,
2386 l_float32 gamma,
2387 l_int32 minval,
2388 l_int32 maxval,
2389 l_int32 targetthresh,
2390 PIX **ppixth,
2391 PIX **ppixb,
2392 PIX **ppixd)
2393 {
2394 PIX *pixe, *pixet, *pixsd, *pixg1, *pixg2, *pixth;
2395
2396 if (ppixth) *ppixth = NULL;
2397 if (ppixb) *ppixb = NULL;
2398 if (ppixd) *ppixd = NULL;
2399 if (!pixs || pixGetDepth(pixs) != 8)
2400 return ERROR_INT("pixs not defined or not 8 bpp", __func__, 1);
2401 if (pixGetColormap(pixs))
2402 return ERROR_INT("pixs is colormapped", __func__, 1);
2403 if (!ppixth && !ppixb && !ppixd)
2404 return ERROR_INT("no output requested", __func__, 1);
2405 if (filtertype != L_SOBEL_EDGE && filtertype != L_TWO_SIDED_EDGE)
2406 return ERROR_INT("invalid filter type", __func__, 1);
2407
2408 /* Get the thresholded edge pixels. These are the ones
2409 * that have values in pixs near the local optimal fg/bg threshold. */
2410 if (filtertype == L_SOBEL_EDGE)
2411 pixe = pixSobelEdgeFilter(pixs, L_VERTICAL_EDGES);
2412 else /* L_TWO_SIDED_EDGE */
2413 pixe = pixTwoSidedEdgeFilter(pixs, L_VERTICAL_EDGES);
2414 pixet = pixThresholdToBinary(pixe, edgethresh);
2415 pixInvert(pixet, pixet);
2416
2417 /* Build a seed image whose only nonzero values are those
2418 * values of pixs corresponding to pixels in the fg of pixet. */
2419 pixsd = pixCreateTemplate(pixs);
2420 pixCombineMasked(pixsd, pixs, pixet);
2421
2422 /* Spread the seed and optionally smooth to reduce noise */
2423 pixg1 = pixSeedspread(pixsd, 4);
2424 pixg2 = pixBlockconv(pixg1, smoothx, smoothy);
2425
2426 /* Optionally do a gamma enhancement */
2427 pixth = pixGammaTRC(NULL, pixg2, gamma, minval, maxval);
2428
2429 /* Do the mapping and thresholding */
2430 if (ppixd) {
2431 *ppixd = pixApplyVariableGrayMap(pixs, pixth, targetthresh);
2432 if (ppixb)
2433 *ppixb = pixThresholdToBinary(*ppixd, targetthresh);
2434 }
2435 else if (ppixb)
2436 *ppixb = pixVarThresholdToBinary(pixs, pixth);
2437
2438 if (ppixth)
2439 *ppixth = pixth;
2440 else
2441 pixDestroy(&pixth);
2442
2443 pixDestroy(&pixe);
2444 pixDestroy(&pixet);
2445 pixDestroy(&pixsd);
2446 pixDestroy(&pixg1);
2447 pixDestroy(&pixg2);
2448 return 0;
2449 }
2450
2451
2452 /*------------------------------------------------------------------*
2453 * Adaptive background normalization (flexible adaptaption) *
2454 *------------------------------------------------------------------*/
2455 /*!
2456 * \brief pixBackgroundNormFlex()
2457 *
2458 * \param[in] pixs 8 bpp grayscale; not colormapped
2459 * \param[in] sx, sy desired tile dimensions; size may vary;
2460 * use values between 3 and 10
2461 * \param[in] smoothx, smoothy half-width of convolution kernel applied to
2462 * threshold array: use values between 1 and 3
2463 * \param[in] delta difference parameter in basin filling;
2464 * use 0 to skip
2465 * \return pixd 8 bpp, background-normalized), or NULL on error
2466 *
2467 * <pre>
2468 * Notes:
2469 * (1) This does adaptation flexibly to a quickly varying background.
2470 * For that reason, all input parameters should be small.
2471 * (2) sx and sy give the tile size; they should be in [5 - 7].
2472 * (3) The full width and height of the convolution kernel
2473 * are (2 * smoothx + 1) and (2 * smoothy + 1). They
2474 * should be in [1 - 2].
2475 * (4) Basin filling is used to fill the large fg regions. The
2476 * parameter %delta measures the height that the black
2477 * background is raised from the local minima. By raising
2478 * the background, it is possible to threshold the large
2479 * fg regions to foreground. If %delta is too large,
2480 * bg regions will be lifted, causing thickening of
2481 * the fg regions. Use 0 to skip.
2482 * </pre>
2483 */
2484 PIX *
2485 pixBackgroundNormFlex(PIX *pixs,
2486 l_int32 sx,
2487 l_int32 sy,
2488 l_int32 smoothx,
2489 l_int32 smoothy,
2490 l_int32 delta)
2491 {
2492 l_float32 scalex, scaley;
2493 PIX *pixt, *pixsd, *pixmin, *pixbg, *pixbgi, *pixd;
2494
2495 if (!pixs || pixGetDepth(pixs) != 8)
2496 return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, NULL);
2497 if (pixGetColormap(pixs))
2498 return (PIX *)ERROR_PTR("pixs is colormapped", __func__, NULL);
2499 if (sx < 3 || sy < 3)
2500 return (PIX *)ERROR_PTR("sx and/or sy less than 3", __func__, NULL);
2501 if (sx > 10 || sy > 10)
2502 return (PIX *)ERROR_PTR("sx and/or sy exceed 10", __func__, NULL);
2503 if (smoothx < 1 || smoothy < 1)
2504 return (PIX *)ERROR_PTR("smooth params less than 1", __func__, NULL);
2505 if (smoothx > 3 || smoothy > 3)
2506 return (PIX *)ERROR_PTR("smooth params exceed 3", __func__, NULL);
2507
2508 /* Generate the bg estimate using smoothed average with subsampling */
2509 scalex = 1. / (l_float32)sx;
2510 scaley = 1. / (l_float32)sy;
2511 pixt = pixScaleSmooth(pixs, scalex, scaley);
2512
2513 /* Do basin filling on the bg estimate if requested */
2514 if (delta <= 0)
2515 pixsd = pixClone(pixt);
2516 else {
2517 pixLocalExtrema(pixt, 0, 0, &pixmin, NULL);
2518 pixsd = pixSeedfillGrayBasin(pixmin, pixt, delta, 4);
2519 pixDestroy(&pixmin);
2520 }
2521 pixbg = pixExtendByReplication(pixsd, 1, 1);
2522
2523 /* Map the bg to 200 */
2524 pixbgi = pixGetInvBackgroundMap(pixbg, 200, smoothx, smoothy);
2525 pixd = pixApplyInvBackgroundGrayMap(pixs, pixbgi, sx, sy);
2526
2527 pixDestroy(&pixt);
2528 pixDestroy(&pixsd);
2529 pixDestroy(&pixbg);
2530 pixDestroy(&pixbgi);
2531 return pixd;
2532 }
2533
2534
2535 /*------------------------------------------------------------------*
2536 * Adaptive contrast normalization *
2537 *------------------------------------------------------------------*/
2538 /*!
2539 * \brief pixContrastNorm()
2540 *
2541 * \param[in] pixd [optional] 8 bpp; null or equal to pixs
2542 * \param[in] pixs 8 bpp grayscale; not colormapped
2543 * \param[in] sx, sy tile dimensions
2544 * \param[in] mindiff minimum difference to accept as valid
2545 * \param[in] smoothx, smoothy half-width of convolution kernel applied to
2546 * min and max arrays: use 0 for no smoothing
2547 * \return pixd always
2548 *
2549 * <pre>
2550 * Notes:
2551 * (1) This function adaptively attempts to expand the contrast
2552 * to the full dynamic range in each tile. If the contrast in
2553 * a tile is smaller than %mindiff, it uses the min and max
2554 * pixel values from neighboring tiles. It also can use
2555 * convolution to smooth the min and max values from
2556 * neighboring tiles. After all that processing, it is
2557 * possible that the actual pixel values in the tile are outside
2558 * the computed [min ... max] range for local contrast
2559 * normalization. Such pixels are taken to be at either 0
2560 * (if below the min) or 255 (if above the max).
2561 * (2) pixd can be equal to pixs (in-place operation) or
2562 * null (makes a new pixd).
2563 * (3) sx and sy give the tile size; they are typically at least 20.
2564 * (4) mindiff is used to eliminate results for tiles where it is
2565 * likely that either fg or bg is missing. A value around 50
2566 * or more is reasonable.
2567 * (5) The full width and height of the convolution kernel
2568 * are (2 * smoothx + 1) and (2 * smoothy + 1). Some smoothing
2569 * is typically useful, and we limit the smoothing half-widths
2570 * to the range from 0 to 8.
2571 * (6) A linear TRC (gamma = 1.0) is applied to increase the contrast
2572 * in each tile. The result can subsequently be globally corrected,
2573 * by applying pixGammaTRC() with arbitrary values of gamma
2574 * and the 0 and 255 points of the mapping.
2575 * </pre>
2576 */
2577 PIX *
2578 pixContrastNorm(PIX *pixd,
2579 PIX *pixs,
2580 l_int32 sx,
2581 l_int32 sy,
2582 l_int32 mindiff,
2583 l_int32 smoothx,
2584 l_int32 smoothy)
2585 {
2586 PIX *pixmin, *pixmax;
2587
2588 if (!pixs || pixGetDepth(pixs) != 8)
2589 return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, pixd);
2590 if (pixd && pixd != pixs)
2591 return (PIX *)ERROR_PTR("pixd not null or == pixs", __func__, pixd);
2592 if (pixGetColormap(pixs))
2593 return (PIX *)ERROR_PTR("pixs is colormapped", __func__, pixd);
2594 if (sx < 5 || sy < 5)
2595 return (PIX *)ERROR_PTR("sx and/or sy less than 5", __func__, pixd);
2596 if (smoothx < 0 || smoothy < 0)
2597 return (PIX *)ERROR_PTR("smooth params less than 0", __func__, pixd);
2598 if (smoothx > 8 || smoothy > 8)
2599 return (PIX *)ERROR_PTR("smooth params exceed 8", __func__, pixd);
2600
2601 /* Get the min and max pixel values in each tile, and represent
2602 * each value as a pixel in pixmin and pixmax, respectively. */
2603 pixMinMaxTiles(pixs, sx, sy, mindiff, smoothx, smoothy, &pixmin, &pixmax);
2604
2605 /* For each tile, do a linear expansion of the dynamic range
2606 * of pixels so that the min value is mapped to 0 and the
2607 * max value is mapped to 255. */
2608 pixd = pixLinearTRCTiled(pixd, pixs, sx, sy, pixmin, pixmax);
2609
2610 pixDestroy(&pixmin);
2611 pixDestroy(&pixmax);
2612 return pixd;
2613 }
2614
2615
2616 /*!
2617 * \brief pixMinMaxTiles()
2618 *
2619 * \param[in] pixs 8 bpp grayscale; not colormapped
2620 * \param[in] sx, sy tile dimensions
2621 * \param[in] mindiff minimum difference to accept as valid
2622 * \param[in] smoothx, smoothy half-width of convolution kernel applied to
2623 * min and max arrays: use 0 for no smoothing
2624 * \param[out] ppixmin tiled minima
2625 * \param[out] ppixmax tiled maxima
2626 * \return 0 if OK, 1 on error
2627 *
2628 * <pre>
2629 * Notes:
2630 * (1) This computes filtered and smoothed values for the min and
2631 * max pixel values in each tile of the image.
2632 * (2) See pixContrastNorm() for usage.
2633 * </pre>
2634 */
2635 static l_ok
2636 pixMinMaxTiles(PIX *pixs,
2637 l_int32 sx,
2638 l_int32 sy,
2639 l_int32 mindiff,
2640 l_int32 smoothx,
2641 l_int32 smoothy,
2642 PIX **ppixmin,
2643 PIX **ppixmax)
2644 {
2645 l_int32 w, h;
2646 PIX *pixmin1, *pixmax1, *pixmin2, *pixmax2;
2647
2648 if (ppixmin) *ppixmin = NULL;
2649 if (ppixmax) *ppixmax = NULL;
2650 if (!ppixmin || !ppixmax)
2651 return ERROR_INT("&pixmin or &pixmax undefined", __func__, 1);
2652 if (!pixs || pixGetDepth(pixs) != 8)
2653 return ERROR_INT("pixs undefined or not 8 bpp", __func__, 1);
2654 if (pixGetColormap(pixs))
2655 return ERROR_INT("pixs is colormapped", __func__, 1);
2656 if (sx < 5 || sy < 5)
2657 return ERROR_INT("sx and/or sy less than 3", __func__, 1);
2658 if (smoothx < 0 || smoothy < 0)
2659 return ERROR_INT("smooth params less than 0", __func__, 1);
2660 if (smoothx > 5 || smoothy > 5)
2661 return ERROR_INT("smooth params exceed 5", __func__, 1);
2662
2663 /* Get the min and max values in each tile */
2664 pixmin1 = pixScaleGrayMinMax(pixs, sx, sy, L_CHOOSE_MIN);
2665 pixmax1 = pixScaleGrayMinMax(pixs, sx, sy, L_CHOOSE_MAX);
2666
2667 pixmin2 = pixExtendByReplication(pixmin1, 1, 1);
2668 pixmax2 = pixExtendByReplication(pixmax1, 1, 1);
2669 pixDestroy(&pixmin1);
2670 pixDestroy(&pixmax1);
2671
2672 /* Make sure no value is 0 */
2673 pixAddConstantGray(pixmin2, 1);
2674 pixAddConstantGray(pixmax2, 1);
2675
2676 /* Generate holes where the contrast is too small */
2677 pixSetLowContrast(pixmin2, pixmax2, mindiff);
2678
2679 /* Fill the holes (0 values) */
2680 pixGetDimensions(pixmin2, &w, &h, NULL);
2681 pixFillMapHoles(pixmin2, w, h, L_FILL_BLACK);
2682 pixFillMapHoles(pixmax2, w, h, L_FILL_BLACK);
2683
2684 /* Smooth if requested */
2685 if (smoothx > 0 || smoothy > 0) {
2686 smoothx = L_MIN(smoothx, (w - 1) / 2);
2687 smoothy = L_MIN(smoothy, (h - 1) / 2);
2688 *ppixmin = pixBlockconv(pixmin2, smoothx, smoothy);
2689 *ppixmax = pixBlockconv(pixmax2, smoothx, smoothy);
2690 }
2691 else {
2692 *ppixmin = pixClone(pixmin2);
2693 *ppixmax = pixClone(pixmax2);
2694 }
2695 pixCopyResolution(*ppixmin, pixs);
2696 pixCopyResolution(*ppixmax, pixs);
2697 pixDestroy(&pixmin2);
2698 pixDestroy(&pixmax2);
2699
2700 return 0;
2701 }
2702
2703
2704 /*!
2705 * \brief pixSetLowContrast()
2706 *
2707 * \param[in] pixs1 8 bpp
2708 * \param[in] pixs2 8 bpp
2709 * \param[in] mindiff minimum difference to accept as valid
2710 * \return 0 if OK; 1 if no pixel diffs are large enough, or on error
2711 *
2712 * <pre>
2713 * Notes:
2714 * (1) This compares corresponding pixels in pixs1 and pixs2.
2715 * When they differ by less than %mindiff, set the pixel
2716 * values to 0 in each. Each pixel typically represents a tile
2717 * in a larger image, and a very small difference between
2718 * the min and max in the tile indicates that the min and max
2719 * values are not to be trusted.
2720 * (2) If contrast (pixel difference) detection is expected to fail,
2721 * caller should check return value.
2722 * </pre>
2723 */
2724 static l_ok
2725 pixSetLowContrast(PIX *pixs1,
2726 PIX *pixs2,
2727 l_int32 mindiff)
2728 {
2729 l_int32 i, j, w, h, d, wpl, val1, val2, found;
2730 l_uint32 *data1, *data2, *line1, *line2;
2731
2732 if (!pixs1 || !pixs2)
2733 return ERROR_INT("pixs1 and pixs2 not both defined", __func__, 1);
2734 if (pixSizesEqual(pixs1, pixs2) == 0)
2735 return ERROR_INT("pixs1 and pixs2 not equal size", __func__, 1);
2736 pixGetDimensions(pixs1, &w, &h, &d);
2737 if (d != 8)
2738 return ERROR_INT("depth not 8 bpp", __func__, 1);
2739 if (mindiff > 254) return 0;
2740
2741 data1 = pixGetData(pixs1);
2742 data2 = pixGetData(pixs2);
2743 wpl = pixGetWpl(pixs1);
2744 found = 0; /* init to not finding any diffs >= mindiff */
2745 for (i = 0; i < h; i++) {
2746 line1 = data1 + i * wpl;
2747 line2 = data2 + i * wpl;
2748 for (j = 0; j < w; j++) {
2749 val1 = GET_DATA_BYTE(line1, j);
2750 val2 = GET_DATA_BYTE(line2, j);
2751 if (L_ABS(val1 - val2) >= mindiff) {
2752 found = 1;
2753 break;
2754 }
2755 }
2756 if (found) break;
2757 }
2758 if (!found) {
2759 L_WARNING("no pixel pair diffs as large as mindiff\n", __func__);
2760 pixClearAll(pixs1);
2761 pixClearAll(pixs2);
2762 return 1;
2763 }
2764
2765 for (i = 0; i < h; i++) {
2766 line1 = data1 + i * wpl;
2767 line2 = data2 + i * wpl;
2768 for (j = 0; j < w; j++) {
2769 val1 = GET_DATA_BYTE(line1, j);
2770 val2 = GET_DATA_BYTE(line2, j);
2771 if (L_ABS(val1 - val2) < mindiff) {
2772 SET_DATA_BYTE(line1, j, 0);
2773 SET_DATA_BYTE(line2, j, 0);
2774 }
2775 }
2776 }
2777
2778 return 0;
2779 }
2780
2781
2782 /*!
2783 * \brief pixLinearTRCTiled()
2784 *
2785 * \param[in] pixd [optional] 8 bpp
2786 * \param[in] pixs 8 bpp, not colormapped
2787 * \param[in] sx, sy tile dimensions
2788 * \param[in] pixmin pix of min values in tiles
2789 * \param[in] pixmax pix of max values in tiles
2790 * \return pixd always
2791 *
2792 * <pre>
2793 * Notes:
2794 * (1) pixd can be equal to pixs (in-place operation) or
2795 * null (makes a new pixd).
2796 * (2) sx and sy give the tile size; they are typically at least 20.
2797 * (3) pixmin and pixmax are generated by pixMinMaxTiles()
2798 * (4) For each tile, this does a linear expansion of the dynamic
2799 * range so that the min value in the tile becomes 0 and the
2800 * max value in the tile becomes 255.
2801 * (5) The LUTs that do the mapping are generated as needed
2802 * and stored for reuse in an integer array within the ptr array iaa[].
2803 * </pre>
2804 */
2805 static PIX *
2806 pixLinearTRCTiled(PIX *pixd,
2807 PIX *pixs,
2808 l_int32 sx,
2809 l_int32 sy,
2810 PIX *pixmin,
2811 PIX *pixmax)
2812 {
2813 l_int32 i, j, k, m, w, h, wt, ht, wpl, wplt, xoff, yoff;
2814 l_int32 minval, maxval, val, sval;
2815 l_int32 *ia;
2816 l_int32 **iaa;
2817 l_uint32 *data, *datamin, *datamax, *line, *tline, *linemin, *linemax;
2818
2819 if (!pixs || pixGetDepth(pixs) != 8)
2820 return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, pixd);
2821 if (pixd && pixd != pixs)
2822 return (PIX *)ERROR_PTR("pixd not null or == pixs", __func__, pixd);
2823 if (pixGetColormap(pixs))
2824 return (PIX *)ERROR_PTR("pixs is colormapped", __func__, pixd);
2825 if (!pixmin || !pixmax)
2826 return (PIX *)ERROR_PTR("pixmin & pixmax not defined", __func__, pixd);
2827 if (sx < 5 || sy < 5)
2828 return (PIX *)ERROR_PTR("sx and/or sy less than 5", __func__, pixd);
2829
2830 iaa = (l_int32 **)LEPT_CALLOC(256, sizeof(l_int32 *));
2831 if ((pixd = pixCopy(pixd, pixs)) == NULL) {
2832 LEPT_FREE(iaa);
2833 return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
2834 }
2835 pixGetDimensions(pixd, &w, &h, NULL);
2836
2837 data = pixGetData(pixd);
2838 wpl = pixGetWpl(pixd);
2839 datamin = pixGetData(pixmin);
2840 datamax = pixGetData(pixmax);
2841 wplt = pixGetWpl(pixmin);
2842 pixGetDimensions(pixmin, &wt, &ht, NULL);
2843 for (i = 0; i < ht; i++) {
2844 line = data + sy * i * wpl;
2845 linemin = datamin + i * wplt;
2846 linemax = datamax + i * wplt;
2847 yoff = sy * i;
2848 for (j = 0; j < wt; j++) {
2849 xoff = sx * j;
2850 minval = GET_DATA_BYTE(linemin, j);
2851 maxval = GET_DATA_BYTE(linemax, j);
2852 if (maxval == minval) {
2853 L_ERROR("shouldn't happen! i,j = %d,%d, minval = %d\n",
2854 __func__, i, j, minval);
2855 continue;
2856 }
2857 if ((ia = iaaGetLinearTRC(iaa, maxval - minval)) == NULL) {
2858 L_ERROR("failure to make ia for j = %d!\n", __func__, j);
2859 continue;
2860 }
2861 for (k = 0; k < sy && yoff + k < h; k++) {
2862 tline = line + k * wpl;
2863 for (m = 0; m < sx && xoff + m < w; m++) {
2864 val = GET_DATA_BYTE(tline, xoff + m);
2865 sval = val - minval;
2866 sval = L_MAX(0, sval);
2867 SET_DATA_BYTE(tline, xoff + m, ia[sval]);
2868 }
2869 }
2870 }
2871 }
2872
2873 for (i = 0; i < 256; i++)
2874 LEPT_FREE(iaa[i]);
2875 LEPT_FREE(iaa);
2876 return pixd;
2877 }
2878
2879
2880 /*!
2881 * \brief iaaGetLinearTRC()
2882 *
2883 * \param[in] iaa bare array of ptrs to l_int32
2884 * \param[in] diff between min and max pixel values that are
2885 * to be mapped to 0 and 255
2886 * \return ia LUT with input (val - minval) and output a
2887 * value between 0 and 255)
2888 */
2889 static l_int32 *
2890 iaaGetLinearTRC(l_int32 **iaa,
2891 l_int32 diff)
2892 {
2893 l_int32 i;
2894 l_int32 *ia;
2895 l_float32 factor;
2896
2897 if (!iaa)
2898 return (l_int32 *)ERROR_PTR("iaa not defined", __func__, NULL);
2899
2900 if (iaa[diff] != NULL) /* already have it */
2901 return iaa[diff];
2902
2903 ia = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
2904 iaa[diff] = ia;
2905 if (diff == 0) { /* shouldn't happen */
2906 for (i = 0; i < 256; i++)
2907 ia[i] = 128;
2908 }
2909 else {
2910 factor = 255. / (l_float32)diff;
2911 for (i = 0; i < diff + 1; i++)
2912 ia[i] = (l_int32)(factor * i + 0.5);
2913 for (i = diff + 1; i < 256; i++)
2914 ia[i] = 255;
2915 }
2916
2917 return ia;
2918 }
2919
2920
2921 /*------------------------------------------------------------------*
2922 * Adaptive normalization with MinMax conversion of RGB to gray, *
2923 * contrast enhancement and optional 2x upscale binarization *
2924 *------------------------------------------------------------------*/
2925 /*!
2926 * \brief pixBackgroundNormTo1MinMax()
2927 *
2928 * \param[in] pixs any depth, with or without colormap
2929 * \param[in] contrast 1 to 10: 1 reduces contrast; 10 is maximum
2930 * enhancement
2931 * \param[in] scalefactor 1 (no change); 2 (2x upscale)
2932 * \return 1 bpp pix if OK; NULL on error
2933 *
2934 * <pre>
2935 * Notes:
2936 * (1) This is a convenience binarization function that does four things:
2937 * * Generates a grayscale image with color enhancement to gray
2938 * * Background normalization
2939 * * Optional contrast enhancement
2940 * * Binarizes either at input resolution or with 2x upscaling
2941 * (2) If the %pixs is 1 bpp, returns a copy.
2942 * (3) The contrast increasing parameter %contrast takes values {1, ... 10}.
2943 * For decent scans, contrast = 1 is recommended. Use a larger
2944 * value if important details are lost in binarization.
2945 * (4) Valid values of %scalefactor are 1 and 2.
2946 * </pre>
2947 */
2948 PIX *
2949 pixBackgroundNormTo1MinMax(PIX *pixs,
2950 l_int32 contrast,
2951 l_int32 scalefactor)
2952 {
2953 PIX *pix1, *pix2, *pixd;
2954
2955 if (!pixs)
2956 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
2957 if (contrast < 1 || contrast > 10)
2958 return (PIX *)ERROR_PTR("contrast not in [1 ... 10]", __func__, NULL);
2959 if (scalefactor != 1 && scalefactor != 2)
2960 return (PIX *)ERROR_PTR("scalefactor not 1 or 2", __func__, NULL);
2961
2962 if (pixGetDepth(pixs) == 1) {
2963 pixd = pixCopy(NULL, pixs);
2964 } else {
2965 pix1 = pixConvertTo8MinMax(pixs);
2966 pix2 = pixBackgroundNormSimple(pix1, NULL, NULL);
2967 pixSelectiveContrastMod(pix2, contrast);
2968 if (scalefactor == 1)
2969 pixd = pixThresholdToBinary(pix2, 180);
2970 else /* scalefactor == 2 */
2971 pixd = pixScaleGray2xLIThresh(pix2, 180);
2972 pixDestroy(&pix1);
2973 pixDestroy(&pix2);
2974 }
2975 return pixd;
2976 }
2977
2978
2979 /*!
2980 * \brief pixConvertTo8MinMax()
2981 *
2982 * \param[in] pixs any depth, with or without colormap
2983 * \return 8 bpp pix if OK; NULL on error
2984 *
2985 * <pre>
2986 * Notes:
2987 * (1) This is a special version of pixConvert1To8() that removes any
2988 * existing colormap and uses pixConvertRGBToGrayMinMax()
2989 * to strongly render color into black.
2990 * </pre>
2991 */
2992 PIX *
2993 pixConvertTo8MinMax(PIX *pixs)
2994 {
2995 l_int32 d;
2996 if (!pixs)
2997 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
2998
2999 d = pixGetDepth(pixs);
3000 if (d == 1) {
3001 return pixConvert1To8(NULL, pixs, 255, 0);
3002 } else if (d == 2) {
3003 return pixConvert2To8(pixs, 0, 85, 170, 255, FALSE);
3004 } else if (d == 4) {
3005 return pixConvert4To8(pixs, FALSE);
3006 } else if (d == 8) {
3007 if (pixGetColormap(pixs) != NULL)
3008 return pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
3009 else
3010 return pixCopy(NULL, pixs);
3011 } else if (d == 16) {
3012 return pixConvert16To8(pixs, L_MS_BYTE);
3013 } else if (d == 32) {
3014 return pixConvertRGBToGrayMinMax(pixs, L_CHOOSE_MIN);
3015 }
3016
3017 L_ERROR("Invalid depth d = %d\n", __func__, d);
3018 return NULL;
3019 }
3020
3021
3022 /*!
3023 * \brief pixSelectiveContrastMod()
3024 *
3025 * \param[in] pixs 8 bpp without colormap
3026 * \param[in] contrast 1 (default value) for some contrast reduction;
3027 * 10 for maximum contrast enhancement.
3028 * \return 0 if OK, 1 on error
3029 *
3030 * <pre>
3031 * Notes:
3032 * (1) This does in-place contrast enhancement on 8 bpp grayscale that
3033 * has been background normalized to 200. Therefore, there should
3034 * be no gray pixels above 200 in %pixs. For general contrast
3035 * enhancement on gray or color images, see pixContrastTRC().
3036 * (2) Caller restricts %contrast to [1 ... 10].
3037 * (3) Use %contrast = 1 for minimum contrast enhancement (which will
3038 * remove some speckle noise) and %contrast = 10 for maximum
3039 * darkening.
3040 * (4) We use 200 for the white point in all transforms. Using a
3041 * white point above 200 will darken all grayscale pixels.
3042 * </pre>
3043 */
3044 static l_ok
3045 pixSelectiveContrastMod(PIX *pixs,
3046 l_int32 contrast)
3047 {
3048 if (!pixs || pixGetDepth(pixs) != 8)
3049 return ERROR_INT("pixs not defined or not 8 bpp", __func__, 1);
3050
3051 if (contrast == 1)
3052 pixGammaTRC(pixs, pixs, 2.0, 50, 200);
3053 else if (contrast == 2)
3054 pixGammaTRC(pixs, pixs, 1.8, 60, 200);
3055 else if (contrast == 3)
3056 pixGammaTRC(pixs, pixs, 1.6, 70, 200);
3057 else if (contrast == 4)
3058 pixGammaTRC(pixs, pixs, 1.4, 80, 200);
3059 else if (contrast == 5)
3060 pixGammaTRC(pixs, pixs, 1.2, 90, 200);
3061 else if (contrast == 6)
3062 pixGammaTRC(pixs, pixs, 1.0, 100, 200);
3063 else if (contrast == 7)
3064 pixGammaTRC(pixs, pixs, 0.85, 110, 200);
3065 else if (contrast == 8)
3066 pixGammaTRC(pixs, pixs, 0.7, 120, 200);
3067 else if (contrast == 9)
3068 pixGammaTRC(pixs, pixs, 0.6, 130, 200);
3069 else /* contrast == 10 */
3070 pixGammaTRC(pixs, pixs, 0.5, 140, 200);
3071
3072 return 0;
3073 }
3074