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