Mercurial > hgrepos > Python2 > PyMuPDF
comparison mupdf-source/thirdparty/leptonica/src/runlength.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 runlength.c | |
| 29 * <pre> | |
| 30 * | |
| 31 * Label pixels by membership in runs | |
| 32 * PIX *pixStrokeWidthTransform() | |
| 33 * static PIX *pixFindMinRunsOrthogonal() | |
| 34 * PIX *pixRunlengthTransform() | |
| 35 * | |
| 36 * Find runs along horizontal and vertical lines | |
| 37 * l_int32 pixFindHorizontalRuns() | |
| 38 * l_int32 pixFindVerticalRuns() | |
| 39 * | |
| 40 * Find max runs along horizontal and vertical lines | |
| 41 * l_int32 pixFindMaxRuns() | |
| 42 * l_int32 pixFindMaxHorizontalRunOnLine() | |
| 43 * l_int32 pixFindMaxVerticalRunOnLine() | |
| 44 * | |
| 45 * Compute runlength-to-membership transform on a line | |
| 46 * l_int32 runlengthMembershipOnLine() | |
| 47 * | |
| 48 * Make byte position LUT | |
| 49 * l_int32 makeMSBitLocTab() | |
| 50 * | |
| 51 * Here we're handling runs of either black or white pixels on 1 bpp | |
| 52 * images. The directions of the runs in the stroke width transform | |
| 53 * are selectable from given sets of angles. Most of the other runs | |
| 54 * are oriented either horizontally along the raster lines or | |
| 55 * vertically along pixel columns. | |
| 56 * </pre> | |
| 57 */ | |
| 58 | |
| 59 #ifdef HAVE_CONFIG_H | |
| 60 #include <config_auto.h> | |
| 61 #endif /* HAVE_CONFIG_H */ | |
| 62 | |
| 63 #include <string.h> | |
| 64 #include <math.h> | |
| 65 #include "allheaders.h" | |
| 66 | |
| 67 static PIX *pixFindMinRunsOrthogonal(PIX *pixs, l_float32 angle, l_int32 depth); | |
| 68 | |
| 69 /*-----------------------------------------------------------------------* | |
| 70 * Label pixels by membership in runs * | |
| 71 *-----------------------------------------------------------------------*/ | |
| 72 /*! | |
| 73 * \brief pixStrokeWidthTransform() | |
| 74 * | |
| 75 * \param[in] pixs 1 bpp | |
| 76 * \param[in] color 0 for white runs, 1 for black runs | |
| 77 * \param[in] depth of pixd: 8 or 16 bpp | |
| 78 * \param[in] nangles 2, 4, 6 or 8 | |
| 79 * \return pixd 8 or 16 bpp, or NULL on error | |
| 80 * | |
| 81 * <pre> | |
| 82 * Notes: | |
| 83 * (1) The dest Pix is 8 or 16 bpp, with the pixel values | |
| 84 * equal to the stroke width in which it is a member. | |
| 85 * The values are clipped to the max pixel value if necessary. | |
| 86 * (2) %color determines if we're labelling white or black strokes. | |
| 87 * (3) A pixel that is not a member of the chosen color gets | |
| 88 * value 0; it belongs to a width of length 0 of the | |
| 89 * chosen color. | |
| 90 * (4) This chooses, for each dest pixel, the minimum of sets | |
| 91 * of runlengths through each pixel. Here are the sets: | |
| 92 * nangles increment set | |
| 93 * ------- --------- -------------------------------- | |
| 94 * 2 90 {0, 90} | |
| 95 * 4 45 {0, 45, 90, 135} | |
| 96 * 6 30 {0, 30, 60, 90, 120, 150} | |
| 97 * 8 22.5 {0, 22.5, 45, 67.5, 90, 112.5, 135, 157.5} | |
| 98 * (5) Runtime scales linearly with (%nangles - 2). | |
| 99 * </pre> | |
| 100 */ | |
| 101 PIX * | |
| 102 pixStrokeWidthTransform(PIX *pixs, | |
| 103 l_int32 color, | |
| 104 l_int32 depth, | |
| 105 l_int32 nangles) | |
| 106 { | |
| 107 l_float32 angle, pi; | |
| 108 PIX *pixh, *pixv, *pixt, *pixg1, *pixg2, *pixg3, *pixg4; | |
| 109 | |
| 110 if (!pixs || pixGetDepth(pixs) != 1) | |
| 111 return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL); | |
| 112 if (depth != 8 && depth != 16) | |
| 113 return (PIX *)ERROR_PTR("depth must be 8 or 16 bpp", __func__, NULL); | |
| 114 if (nangles != 2 && nangles != 4 && nangles != 6 && nangles != 8) | |
| 115 return (PIX *)ERROR_PTR("nangles not in {2,4,6,8}", __func__, NULL); | |
| 116 | |
| 117 /* Use fg runs for evaluation */ | |
| 118 if (color == 0) | |
| 119 pixt = pixInvert(NULL, pixs); | |
| 120 else | |
| 121 pixt = pixClone(pixs); | |
| 122 | |
| 123 /* Find min length at 0 and 90 degrees */ | |
| 124 pixh = pixRunlengthTransform(pixt, 1, L_HORIZONTAL_RUNS, depth); | |
| 125 pixv = pixRunlengthTransform(pixt, 1, L_VERTICAL_RUNS, depth); | |
| 126 pixg1 = pixMinOrMax(NULL, pixh, pixv, L_CHOOSE_MIN); | |
| 127 pixDestroy(&pixh); | |
| 128 pixDestroy(&pixv); | |
| 129 | |
| 130 pixg2 = pixg3 = pixg4 = NULL; | |
| 131 pi = 3.1415926535f; | |
| 132 if (nangles == 4 || nangles == 8) { | |
| 133 /* Find min length at +45 and -45 degrees */ | |
| 134 angle = pi / 4.0f; | |
| 135 pixg2 = pixFindMinRunsOrthogonal(pixt, angle, depth); | |
| 136 } | |
| 137 | |
| 138 if (nangles == 6) { | |
| 139 /* Find min length at +30 and -60 degrees */ | |
| 140 angle = pi / 6.0f; | |
| 141 pixg2 = pixFindMinRunsOrthogonal(pixt, angle, depth); | |
| 142 | |
| 143 /* Find min length at +60 and -30 degrees */ | |
| 144 angle = pi / 3.0f; | |
| 145 pixg3 = pixFindMinRunsOrthogonal(pixt, angle, depth); | |
| 146 } | |
| 147 | |
| 148 if (nangles == 8) { | |
| 149 /* Find min length at +22.5 and -67.5 degrees */ | |
| 150 angle = pi / 8.0f; | |
| 151 pixg3 = pixFindMinRunsOrthogonal(pixt, angle, depth); | |
| 152 | |
| 153 /* Find min length at +67.5 and -22.5 degrees */ | |
| 154 angle = 3.0 * pi / 8.0f; | |
| 155 pixg4 = pixFindMinRunsOrthogonal(pixt, angle, depth); | |
| 156 } | |
| 157 pixDestroy(&pixt); | |
| 158 | |
| 159 if (nangles > 2) | |
| 160 pixMinOrMax(pixg1, pixg1, pixg2, L_CHOOSE_MIN); | |
| 161 if (nangles > 4) | |
| 162 pixMinOrMax(pixg1, pixg1, pixg3, L_CHOOSE_MIN); | |
| 163 if (nangles > 6) | |
| 164 pixMinOrMax(pixg1, pixg1, pixg4, L_CHOOSE_MIN); | |
| 165 pixDestroy(&pixg2); | |
| 166 pixDestroy(&pixg3); | |
| 167 pixDestroy(&pixg4); | |
| 168 return pixg1; | |
| 169 } | |
| 170 | |
| 171 | |
| 172 /*! | |
| 173 * \brief pixFindMinRunsOrthogonal() | |
| 174 * | |
| 175 * \param[in] pixs 1 bpp | |
| 176 * \param[in] angle in radians | |
| 177 * \param[in] depth of pixd: 8 or 16 bpp | |
| 178 * \return pixd 8 or 16 bpp, or NULL on error | |
| 179 * | |
| 180 * <pre> | |
| 181 * Notes: | |
| 182 * (1) This computes, for each fg pixel in pixs, the minimum of | |
| 183 * the runlengths going through that pixel in two orthogonal | |
| 184 * directions: at %angle and at (90 + %angle). | |
| 185 * (2) We use rotation by shear because the forward and backward | |
| 186 * rotations by the same angle are exact inverse operations. | |
| 187 * As a result, the nonzero pixels in pixd correspond exactly | |
| 188 * to the fg pixels in pixs. This is not the case with | |
| 189 * sampled rotation, due to spatial quantization. Nevertheless, | |
| 190 * the result suffers from lack of exact correspondence | |
| 191 * between original and rotated pixels, also due to spatial | |
| 192 * quantization, causing some boundary pixels to be | |
| 193 * shifted from bg to fg or v.v. | |
| 194 * </pre> | |
| 195 */ | |
| 196 static PIX * | |
| 197 pixFindMinRunsOrthogonal(PIX *pixs, | |
| 198 l_float32 angle, | |
| 199 l_int32 depth) | |
| 200 { | |
| 201 l_int32 w, h, diag, xoff, yoff; | |
| 202 PIX *pixb, *pixr, *pixh, *pixv, *pixg1, *pixg2, *pixd; | |
| 203 BOX *box; | |
| 204 | |
| 205 if (!pixs || pixGetDepth(pixs) != 1) | |
| 206 return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL); | |
| 207 | |
| 208 /* Rasterop into the center of a sufficiently large image | |
| 209 * so we don't lose pixels for any rotation angle. */ | |
| 210 pixGetDimensions(pixs, &w, &h, NULL); | |
| 211 diag = (l_int32)(sqrt((l_float64)(w * w + h * h)) + 2.5); | |
| 212 xoff = (diag - w) / 2; | |
| 213 yoff = (diag - h) / 2; | |
| 214 pixb = pixCreate(diag, diag, 1); | |
| 215 pixRasterop(pixb, xoff, yoff, w, h, PIX_SRC, pixs, 0, 0); | |
| 216 | |
| 217 /* Rotate about the 'center', get the min of orthogonal transforms, | |
| 218 * rotate back, and crop the part corresponding to pixs. */ | |
| 219 pixr = pixRotateShear(pixb, diag / 2, diag / 2, angle, L_BRING_IN_WHITE); | |
| 220 pixh = pixRunlengthTransform(pixr, 1, L_HORIZONTAL_RUNS, depth); | |
| 221 pixv = pixRunlengthTransform(pixr, 1, L_VERTICAL_RUNS, depth); | |
| 222 pixg1 = pixMinOrMax(NULL, pixh, pixv, L_CHOOSE_MIN); | |
| 223 pixg2 = pixRotateShear(pixg1, diag / 2, diag / 2, -angle, L_BRING_IN_WHITE); | |
| 224 box = boxCreate(xoff, yoff, w, h); | |
| 225 pixd = pixClipRectangle(pixg2, box, NULL); | |
| 226 | |
| 227 pixDestroy(&pixb); | |
| 228 pixDestroy(&pixr); | |
| 229 pixDestroy(&pixh); | |
| 230 pixDestroy(&pixv); | |
| 231 pixDestroy(&pixg1); | |
| 232 pixDestroy(&pixg2); | |
| 233 boxDestroy(&box); | |
| 234 return pixd; | |
| 235 } | |
| 236 | |
| 237 | |
| 238 /*! | |
| 239 * \brief pixRunlengthTransform() | |
| 240 * | |
| 241 * \param[in] pixs 1 bpp | |
| 242 * \param[in] color 0 for white runs, 1 for black runs | |
| 243 * \param[in] direction L_HORIZONTAL_RUNS, L_VERTICAL_RUNS | |
| 244 * \param[in] depth 8 or 16 bpp | |
| 245 * \return pixd 8 or 16 bpp, or NULL on error | |
| 246 * | |
| 247 * <pre> | |
| 248 * Notes: | |
| 249 * (1) The dest Pix is 8 or 16 bpp, with the pixel values | |
| 250 * equal to the runlength in which it is a member. | |
| 251 * The length is clipped to the max pixel value if necessary. | |
| 252 * (2) %color determines if we're labelling white or black runs. | |
| 253 * (3) A pixel that is not a member of the chosen color gets | |
| 254 * value 0; it belongs to a run of length 0 of the | |
| 255 * chosen color. | |
| 256 * (4) To convert for maximum dynamic range, either linear or | |
| 257 * log, use pixMaxDynamicRange(). | |
| 258 * </pre> | |
| 259 */ | |
| 260 PIX * | |
| 261 pixRunlengthTransform(PIX *pixs, | |
| 262 l_int32 color, | |
| 263 l_int32 direction, | |
| 264 l_int32 depth) | |
| 265 { | |
| 266 l_int32 i, j, w, h, wpld, bufsize, maxsize, n; | |
| 267 l_int32 *start, *end, *buffer; | |
| 268 l_uint32 *datad, *lined; | |
| 269 PIX *pixt, *pixd; | |
| 270 | |
| 271 if (!pixs) | |
| 272 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); | |
| 273 if (pixGetDepth(pixs) != 1) | |
| 274 return (PIX *)ERROR_PTR("pixs not 1 bpp", __func__, NULL); | |
| 275 if (depth != 8 && depth != 16) | |
| 276 return (PIX *)ERROR_PTR("depth must be 8 or 16 bpp", __func__, NULL); | |
| 277 | |
| 278 pixGetDimensions(pixs, &w, &h, NULL); | |
| 279 if (direction == L_HORIZONTAL_RUNS) | |
| 280 maxsize = 1 + w / 2; | |
| 281 else if (direction == L_VERTICAL_RUNS) | |
| 282 maxsize = 1 + h / 2; | |
| 283 else | |
| 284 return (PIX *)ERROR_PTR("invalid direction", __func__, NULL); | |
| 285 bufsize = L_MAX(w, h); | |
| 286 if (bufsize > 1000000) { | |
| 287 L_ERROR("largest image dimension = %d; too big\n", __func__, bufsize); | |
| 288 return NULL; | |
| 289 } | |
| 290 | |
| 291 if ((pixd = pixCreate(w, h, depth)) == NULL) | |
| 292 return (PIX *)ERROR_PTR("pixd not made", __func__, NULL); | |
| 293 datad = pixGetData(pixd); | |
| 294 wpld = pixGetWpl(pixd); | |
| 295 | |
| 296 start = (l_int32 *)LEPT_CALLOC(maxsize, sizeof(l_int32)); | |
| 297 end = (l_int32 *)LEPT_CALLOC(maxsize, sizeof(l_int32)); | |
| 298 buffer = (l_int32 *)LEPT_CALLOC(bufsize, sizeof(l_int32)); | |
| 299 | |
| 300 /* Use fg runs for evaluation */ | |
| 301 if (color == 0) | |
| 302 pixt = pixInvert(NULL, pixs); | |
| 303 else | |
| 304 pixt = pixClone(pixs); | |
| 305 | |
| 306 if (direction == L_HORIZONTAL_RUNS) { | |
| 307 for (i = 0; i < h; i++) { | |
| 308 pixFindHorizontalRuns(pixt, i, start, end, &n); | |
| 309 runlengthMembershipOnLine(buffer, w, depth, start, end, n); | |
| 310 lined = datad + i * wpld; | |
| 311 if (depth == 8) { | |
| 312 for (j = 0; j < w; j++) | |
| 313 SET_DATA_BYTE(lined, j, buffer[j]); | |
| 314 } else { /* depth == 16 */ | |
| 315 for (j = 0; j < w; j++) | |
| 316 SET_DATA_TWO_BYTES(lined, j, buffer[j]); | |
| 317 } | |
| 318 } | |
| 319 } else { /* L_VERTICAL_RUNS */ | |
| 320 for (j = 0; j < w; j++) { | |
| 321 pixFindVerticalRuns(pixt, j, start, end, &n); | |
| 322 runlengthMembershipOnLine(buffer, h, depth, start, end, n); | |
| 323 if (depth == 8) { | |
| 324 for (i = 0; i < h; i++) { | |
| 325 lined = datad + i * wpld; | |
| 326 SET_DATA_BYTE(lined, j, buffer[i]); | |
| 327 } | |
| 328 } else { /* depth == 16 */ | |
| 329 for (i = 0; i < h; i++) { | |
| 330 lined = datad + i * wpld; | |
| 331 SET_DATA_TWO_BYTES(lined, j, buffer[i]); | |
| 332 } | |
| 333 } | |
| 334 } | |
| 335 } | |
| 336 | |
| 337 pixDestroy(&pixt); | |
| 338 LEPT_FREE(start); | |
| 339 LEPT_FREE(end); | |
| 340 LEPT_FREE(buffer); | |
| 341 return pixd; | |
| 342 } | |
| 343 | |
| 344 | |
| 345 /*-----------------------------------------------------------------------* | |
| 346 * Find runs along horizontal and vertical lines * | |
| 347 *-----------------------------------------------------------------------*/ | |
| 348 /*! | |
| 349 * \brief pixFindHorizontalRuns() | |
| 350 * | |
| 351 * \param[in] pix 1 bpp | |
| 352 * \param[in] y line to traverse | |
| 353 * \param[in] xstart returns array of start positions for fg runs | |
| 354 * \param[in] xend returns array of end positions for fg runs | |
| 355 * \param[out] pn the number of runs found | |
| 356 * \return 0 if OK; 1 on error | |
| 357 * | |
| 358 * <pre> | |
| 359 * Notes: | |
| 360 * (1) This finds foreground horizontal runs on a single scanline. | |
| 361 * (2) To find background runs, use pixInvert() before applying | |
| 362 * this function. | |
| 363 * (3) %xstart and %xend arrays are input. They should be | |
| 364 * of size w/2 + 1 to insure that they can hold | |
| 365 * the maximum number of runs in the raster line. | |
| 366 * </pre> | |
| 367 */ | |
| 368 l_ok | |
| 369 pixFindHorizontalRuns(PIX *pix, | |
| 370 l_int32 y, | |
| 371 l_int32 *xstart, | |
| 372 l_int32 *xend, | |
| 373 l_int32 *pn) | |
| 374 { | |
| 375 l_int32 inrun; /* boolean */ | |
| 376 l_int32 index, w, h, d, j, wpl, val; | |
| 377 l_uint32 *line; | |
| 378 | |
| 379 if (!pn) | |
| 380 return ERROR_INT("&n not defined", __func__, 1); | |
| 381 *pn = 0; | |
| 382 if (!pix) | |
| 383 return ERROR_INT("pix not defined", __func__, 1); | |
| 384 pixGetDimensions(pix, &w, &h, &d); | |
| 385 if (d != 1) | |
| 386 return ERROR_INT("pix not 1 bpp", __func__, 1); | |
| 387 if (y < 0 || y >= h) | |
| 388 return ERROR_INT("y not in [0 ... h - 1]", __func__, 1); | |
| 389 if (!xstart) | |
| 390 return ERROR_INT("xstart not defined", __func__, 1); | |
| 391 if (!xend) | |
| 392 return ERROR_INT("xend not defined", __func__, 1); | |
| 393 | |
| 394 wpl = pixGetWpl(pix); | |
| 395 line = pixGetData(pix) + y * wpl; | |
| 396 | |
| 397 inrun = FALSE; | |
| 398 index = 0; | |
| 399 for (j = 0; j < w; j++) { | |
| 400 val = GET_DATA_BIT(line, j); | |
| 401 if (!inrun) { | |
| 402 if (val) { | |
| 403 xstart[index] = j; | |
| 404 inrun = TRUE; | |
| 405 } | |
| 406 } else { | |
| 407 if (!val) { | |
| 408 xend[index++] = j - 1; | |
| 409 inrun = FALSE; | |
| 410 } | |
| 411 } | |
| 412 } | |
| 413 | |
| 414 /* Finish last run if necessary */ | |
| 415 if (inrun) | |
| 416 xend[index++] = w - 1; | |
| 417 | |
| 418 *pn = index; | |
| 419 return 0; | |
| 420 } | |
| 421 | |
| 422 | |
| 423 /*! | |
| 424 * \brief pixFindVerticalRuns() | |
| 425 * | |
| 426 * \param[in] pix 1 bpp | |
| 427 * \param[in] x line to traverse | |
| 428 * \param[in] ystart returns array of start positions for fg runs | |
| 429 * \param[in] yend returns array of end positions for fg runs | |
| 430 * \param[out] pn the number of runs found | |
| 431 * \return 0 if OK; 1 on error | |
| 432 * | |
| 433 * <pre> | |
| 434 * Notes: | |
| 435 * (1) This finds foreground vertical runs on a single scanline. | |
| 436 * (2) To find background runs, use pixInvert() before applying | |
| 437 * this function. | |
| 438 * (3) %ystart and %yend arrays are input. They should be | |
| 439 * of size h/2 + 1 to insure that they can hold | |
| 440 * the maximum number of runs in the raster line. | |
| 441 * </pre> | |
| 442 */ | |
| 443 l_ok | |
| 444 pixFindVerticalRuns(PIX *pix, | |
| 445 l_int32 x, | |
| 446 l_int32 *ystart, | |
| 447 l_int32 *yend, | |
| 448 l_int32 *pn) | |
| 449 { | |
| 450 l_int32 inrun; /* boolean */ | |
| 451 l_int32 index, w, h, d, i, wpl, val; | |
| 452 l_uint32 *data, *line; | |
| 453 | |
| 454 if (!pn) | |
| 455 return ERROR_INT("&n not defined", __func__, 1); | |
| 456 *pn = 0; | |
| 457 if (!pix) | |
| 458 return ERROR_INT("pix not defined", __func__, 1); | |
| 459 pixGetDimensions(pix, &w, &h, &d); | |
| 460 if (d != 1) | |
| 461 return ERROR_INT("pix not 1 bpp", __func__, 1); | |
| 462 if (x < 0 || x >= w) | |
| 463 return ERROR_INT("x not in [0 ... w - 1]", __func__, 1); | |
| 464 if (!ystart) | |
| 465 return ERROR_INT("ystart not defined", __func__, 1); | |
| 466 if (!yend) | |
| 467 return ERROR_INT("yend not defined", __func__, 1); | |
| 468 | |
| 469 wpl = pixGetWpl(pix); | |
| 470 data = pixGetData(pix); | |
| 471 | |
| 472 inrun = FALSE; | |
| 473 index = 0; | |
| 474 for (i = 0; i < h; i++) { | |
| 475 line = data + i * wpl; | |
| 476 val = GET_DATA_BIT(line, x); | |
| 477 if (!inrun) { | |
| 478 if (val) { | |
| 479 ystart[index] = i; | |
| 480 inrun = TRUE; | |
| 481 } | |
| 482 } else { | |
| 483 if (!val) { | |
| 484 yend[index++] = i - 1; | |
| 485 inrun = FALSE; | |
| 486 } | |
| 487 } | |
| 488 } | |
| 489 | |
| 490 /* Finish last run if necessary */ | |
| 491 if (inrun) | |
| 492 yend[index++] = h - 1; | |
| 493 | |
| 494 *pn = index; | |
| 495 return 0; | |
| 496 } | |
| 497 | |
| 498 | |
| 499 /*-----------------------------------------------------------------------* | |
| 500 * Find max runs along horizontal and vertical lines * | |
| 501 *-----------------------------------------------------------------------*/ | |
| 502 /*! | |
| 503 * \brief pixFindMaxRuns() | |
| 504 * | |
| 505 * \param[in] pix 1 bpp | |
| 506 * \param[in] direction L_HORIZONTAL_RUNS or L_VERTICAL_RUNS | |
| 507 * \param[out] pnastart [optional] start locations of longest runs | |
| 508 * \return na of lengths of runs, or NULL on error | |
| 509 * | |
| 510 * <pre> | |
| 511 * Notes: | |
| 512 * (1) This finds the longest foreground runs by row or column | |
| 513 * (2) To find background runs, use pixInvert() before applying | |
| 514 * this function. | |
| 515 * </pre> | |
| 516 */ | |
| 517 NUMA * | |
| 518 pixFindMaxRuns(PIX *pix, | |
| 519 l_int32 direction, | |
| 520 NUMA **pnastart) | |
| 521 { | |
| 522 l_int32 w, h, i, start, size; | |
| 523 NUMA *nasize; | |
| 524 | |
| 525 if (pnastart) *pnastart = NULL; | |
| 526 if (direction != L_HORIZONTAL_RUNS && direction != L_VERTICAL_RUNS) | |
| 527 return (NUMA *)ERROR_PTR("direction invalid", __func__, NULL); | |
| 528 if (!pix || pixGetDepth(pix) != 1) | |
| 529 return (NUMA *)ERROR_PTR("pix undefined or not 1 bpp", __func__, NULL); | |
| 530 | |
| 531 pixGetDimensions(pix, &w, &h, NULL); | |
| 532 nasize = numaCreate(w); | |
| 533 if (pnastart) *pnastart = numaCreate(w); | |
| 534 if (direction == L_HORIZONTAL_RUNS) { | |
| 535 for (i = 0; i < h; i++) { | |
| 536 pixFindMaxHorizontalRunOnLine(pix, i, &start, &size); | |
| 537 numaAddNumber(nasize, size); | |
| 538 if (pnastart) numaAddNumber(*pnastart, start); | |
| 539 } | |
| 540 } else { /* vertical scans */ | |
| 541 for (i = 0; i < w; i++) { | |
| 542 pixFindMaxVerticalRunOnLine(pix, i, &start, &size); | |
| 543 numaAddNumber(nasize, size); | |
| 544 if (pnastart) numaAddNumber(*pnastart, start); | |
| 545 } | |
| 546 } | |
| 547 | |
| 548 return nasize; | |
| 549 } | |
| 550 | |
| 551 | |
| 552 /*! | |
| 553 * \brief pixFindMaxHorizontalRunOnLine() | |
| 554 * | |
| 555 * \param[in] pix 1 bpp | |
| 556 * \param[in] y line to traverse | |
| 557 * \param[out] pxstart [optional] start position | |
| 558 * \param[out] psize the size of the run | |
| 559 * \return 0 if OK; 1 on error | |
| 560 * | |
| 561 * <pre> | |
| 562 * Notes: | |
| 563 * (1) This finds the longest foreground horizontal run on a scanline. | |
| 564 * (2) To find background runs, use pixInvert() before applying | |
| 565 * this function. | |
| 566 * </pre> | |
| 567 */ | |
| 568 l_ok | |
| 569 pixFindMaxHorizontalRunOnLine(PIX *pix, | |
| 570 l_int32 y, | |
| 571 l_int32 *pxstart, | |
| 572 l_int32 *psize) | |
| 573 { | |
| 574 l_int32 inrun; /* boolean */ | |
| 575 l_int32 w, h, j, wpl, val, maxstart, maxsize, length, start; | |
| 576 l_uint32 *line; | |
| 577 | |
| 578 if (pxstart) *pxstart = 0; | |
| 579 if (!psize) | |
| 580 return ERROR_INT("&size not defined", __func__, 1); | |
| 581 *psize = 0; | |
| 582 if (!pix || pixGetDepth(pix) != 1) | |
| 583 return ERROR_INT("pix not defined or not 1 bpp", __func__, 1); | |
| 584 pixGetDimensions(pix, &w, &h, NULL); | |
| 585 if (y < 0 || y >= h) | |
| 586 return ERROR_INT("y not in [0 ... h - 1]", __func__, 1); | |
| 587 | |
| 588 wpl = pixGetWpl(pix); | |
| 589 line = pixGetData(pix) + y * wpl; | |
| 590 inrun = FALSE; | |
| 591 start = 0; | |
| 592 maxstart = 0; | |
| 593 maxsize = 0; | |
| 594 for (j = 0; j < w; j++) { | |
| 595 val = GET_DATA_BIT(line, j); | |
| 596 if (!inrun) { | |
| 597 if (val) { | |
| 598 start = j; | |
| 599 inrun = TRUE; | |
| 600 } | |
| 601 } else if (!val) { /* run just ended */ | |
| 602 length = j - start; | |
| 603 if (length > maxsize) { | |
| 604 maxsize = length; | |
| 605 maxstart = start; | |
| 606 } | |
| 607 inrun = FALSE; | |
| 608 } | |
| 609 } | |
| 610 | |
| 611 if (inrun) { /* a run has continued to the end of the row */ | |
| 612 length = j - start; | |
| 613 if (length > maxsize) { | |
| 614 maxsize = length; | |
| 615 maxstart = start; | |
| 616 } | |
| 617 } | |
| 618 if (pxstart) *pxstart = maxstart; | |
| 619 *psize = maxsize; | |
| 620 return 0; | |
| 621 } | |
| 622 | |
| 623 | |
| 624 /*! | |
| 625 * \brief pixFindMaxVerticalRunOnLine() | |
| 626 * | |
| 627 * \param[in] pix 1 bpp | |
| 628 * \param[in] x column to traverse | |
| 629 * \param[out] pystart [optional] start position | |
| 630 * \param[out] psize the size of the run | |
| 631 * \return 0 if OK; 1 on error | |
| 632 * | |
| 633 * <pre> | |
| 634 * Notes: | |
| 635 * (1) This finds the longest foreground vertical run on a scanline. | |
| 636 * (2) To find background runs, use pixInvert() before applying | |
| 637 * this function. | |
| 638 * </pre> | |
| 639 */ | |
| 640 l_ok | |
| 641 pixFindMaxVerticalRunOnLine(PIX *pix, | |
| 642 l_int32 x, | |
| 643 l_int32 *pystart, | |
| 644 l_int32 *psize) | |
| 645 { | |
| 646 l_int32 inrun; /* boolean */ | |
| 647 l_int32 w, h, i, wpl, val, maxstart, maxsize, length, start; | |
| 648 l_uint32 *data, *line; | |
| 649 | |
| 650 if (pystart) *pystart = 0; | |
| 651 if (!psize) | |
| 652 return ERROR_INT("&size not defined", __func__, 1); | |
| 653 *psize = 0; | |
| 654 if (!pix || pixGetDepth(pix) != 1) | |
| 655 return ERROR_INT("pix not defined or not 1 bpp", __func__, 1); | |
| 656 pixGetDimensions(pix, &w, &h, NULL); | |
| 657 if (x < 0 || x >= w) | |
| 658 return ERROR_INT("x not in [0 ... w - 1]", __func__, 1); | |
| 659 | |
| 660 wpl = pixGetWpl(pix); | |
| 661 data = pixGetData(pix); | |
| 662 inrun = FALSE; | |
| 663 start = 0; | |
| 664 maxstart = 0; | |
| 665 maxsize = 0; | |
| 666 for (i = 0; i < h; i++) { | |
| 667 line = data + i * wpl; | |
| 668 val = GET_DATA_BIT(line, x); | |
| 669 if (!inrun) { | |
| 670 if (val) { | |
| 671 start = i; | |
| 672 inrun = TRUE; | |
| 673 } | |
| 674 } else if (!val) { /* run just ended */ | |
| 675 length = i - start; | |
| 676 if (length > maxsize) { | |
| 677 maxsize = length; | |
| 678 maxstart = start; | |
| 679 } | |
| 680 inrun = FALSE; | |
| 681 } | |
| 682 } | |
| 683 | |
| 684 if (inrun) { /* a run has continued to the end of the column */ | |
| 685 length = i - start; | |
| 686 if (length > maxsize) { | |
| 687 maxsize = length; | |
| 688 maxstart = start; | |
| 689 } | |
| 690 } | |
| 691 if (pystart) *pystart = maxstart; | |
| 692 *psize = maxsize; | |
| 693 return 0; | |
| 694 } | |
| 695 | |
| 696 | |
| 697 /*-----------------------------------------------------------------------* | |
| 698 * Compute runlength-to-membership transform on a line * | |
| 699 *-----------------------------------------------------------------------*/ | |
| 700 /*! | |
| 701 * \brief runlengthMembershipOnLine() | |
| 702 * | |
| 703 * \param[in] buffer into which full line of data is placed | |
| 704 * \param[in] size full size of line; w or h | |
| 705 * \param[in] depth 8 or 16 bpp | |
| 706 * \param[in] start array of start positions for fg runs | |
| 707 * \param[in] end array of end positions for fg runs | |
| 708 * \param[in] n the number of runs | |
| 709 * \return 0 if OK; 1 on error | |
| 710 * | |
| 711 * <pre> | |
| 712 * Notes: | |
| 713 * (1) Converts a set of runlengths into a buffer of | |
| 714 * runlength membership values. | |
| 715 * (2) Initialization of the array gives pixels that are | |
| 716 * not within a run the value 0. | |
| 717 * </pre> | |
| 718 */ | |
| 719 l_ok | |
| 720 runlengthMembershipOnLine(l_int32 *buffer, | |
| 721 l_int32 size, | |
| 722 l_int32 depth, | |
| 723 l_int32 *start, | |
| 724 l_int32 *end, | |
| 725 l_int32 n) | |
| 726 { | |
| 727 l_int32 i, j, first, last, diff, max; | |
| 728 | |
| 729 if (!buffer) | |
| 730 return ERROR_INT("buffer not defined", __func__, 1); | |
| 731 if (!start) | |
| 732 return ERROR_INT("start not defined", __func__, 1); | |
| 733 if (!end) | |
| 734 return ERROR_INT("end not defined", __func__, 1); | |
| 735 | |
| 736 if (depth == 8) | |
| 737 max = 0xff; | |
| 738 else /* depth == 16 */ | |
| 739 max = 0xffff; | |
| 740 | |
| 741 memset(buffer, 0, 4 * size); | |
| 742 for (i = 0; i < n; i++) { | |
| 743 first = start[i]; | |
| 744 last = end[i]; | |
| 745 diff = last - first + 1; | |
| 746 diff = L_MIN(diff, max); | |
| 747 for (j = first; j <= last; j++) | |
| 748 buffer[j] = diff; | |
| 749 } | |
| 750 | |
| 751 return 0; | |
| 752 } | |
| 753 | |
| 754 | |
| 755 /*-----------------------------------------------------------------------* | |
| 756 * Make byte position LUT * | |
| 757 *-----------------------------------------------------------------------*/ | |
| 758 /*! | |
| 759 * \brief makeMSBitLocTab() | |
| 760 * | |
| 761 * \param[in] bitval either 0 or 1 | |
| 762 * \return table: for an input byte, the MS bit location, starting at 0 | |
| 763 * with the MSBit in the byte, or NULL on error. | |
| 764 * | |
| 765 * <pre> | |
| 766 * Notes: | |
| 767 * (1) If %bitval == 1, it finds the leftmost ON pixel in a byte; | |
| 768 * otherwise if %bitval == 0, it finds the leftmost OFF pixel. | |
| 769 * (2) If there are no pixels of the indicated color in the byte, | |
| 770 * this returns 8. | |
| 771 * </pre> | |
| 772 */ | |
| 773 l_int32 * | |
| 774 makeMSBitLocTab(l_int32 bitval) | |
| 775 { | |
| 776 l_int32 i, j; | |
| 777 l_int32 *tab; | |
| 778 l_uint8 byte, mask; | |
| 779 | |
| 780 tab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32)); | |
| 781 for (i = 0; i < 256; i++) { | |
| 782 byte = (l_uint8)i; | |
| 783 if (bitval == 0) | |
| 784 byte = ~byte; | |
| 785 tab[i] = 8; | |
| 786 mask = 0x80; | |
| 787 for (j = 0; j < 8; j++) { | |
| 788 if (byte & mask) { | |
| 789 tab[i] = j; | |
| 790 break; | |
| 791 } | |
| 792 mask >>= 1; | |
| 793 } | |
| 794 } | |
| 795 return tab; | |
| 796 } |
