Mercurial > hgrepos > Python2 > PyMuPDF
comparison mupdf-source/thirdparty/leptonica/src/edge.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 edge.c | |
| 29 * <pre> | |
| 30 * | |
| 31 * Sobel edge detecting filter | |
| 32 * PIX *pixSobelEdgeFilter() | |
| 33 * | |
| 34 * Two-sided edge gradient filter | |
| 35 * PIX *pixTwoSidedEdgeFilter() | |
| 36 * | |
| 37 * Measurement of edge smoothness | |
| 38 * l_int32 pixMeasureEdgeSmoothness() | |
| 39 * NUMA *pixGetEdgeProfile() | |
| 40 * l_int32 pixGetLastOffPixelInRun() | |
| 41 * l_int32 pixGetLastOnPixelInRun() | |
| 42 * | |
| 43 * | |
| 44 * The Sobel edge detector uses these two simple gradient filters. | |
| 45 * | |
| 46 * 1 2 1 1 0 -1 | |
| 47 * 0 0 0 2 0 -2 | |
| 48 * -1 -2 -1 1 0 -1 | |
| 49 * | |
| 50 * (horizontal) (vertical) | |
| 51 * | |
| 52 * To use both the vertical and horizontal filters, set the orientation | |
| 53 * flag to L_ALL_EDGES; this sums the abs. value of their outputs, | |
| 54 * clipped to 255. | |
| 55 * | |
| 56 * See comments below for displaying the resulting image with | |
| 57 * the edges dark, both for 8 bpp and 1 bpp. | |
| 58 * </pre> | |
| 59 */ | |
| 60 | |
| 61 #ifdef HAVE_CONFIG_H | |
| 62 #include <config_auto.h> | |
| 63 #endif /* HAVE_CONFIG_H */ | |
| 64 | |
| 65 #include "allheaders.h" | |
| 66 | |
| 67 /*----------------------------------------------------------------------* | |
| 68 * Sobel edge detecting filter * | |
| 69 *----------------------------------------------------------------------*/ | |
| 70 /*! | |
| 71 * \brief pixSobelEdgeFilter() | |
| 72 * | |
| 73 * \param[in] pixs 8 bpp; no colormap | |
| 74 * \param[in] orientflag L_HORIZONTAL_EDGES, L_VERTICAL_EDGES, L_ALL_EDGES | |
| 75 * \return pixd 8 bpp, edges are brighter, or NULL on error | |
| 76 * | |
| 77 * <pre> | |
| 78 * Notes: | |
| 79 * (1) Invert pixd to see larger gradients as darker (grayscale). | |
| 80 * (2) To generate a binary image of the edges, threshold | |
| 81 * the result using pixThresholdToBinary(). If the high | |
| 82 * edge values are to be fg (1), invert after running | |
| 83 * pixThresholdToBinary(). | |
| 84 * (3) Label the pixels as follows: | |
| 85 * 1 4 7 | |
| 86 * 2 5 8 | |
| 87 * 3 6 9 | |
| 88 * Read the data incrementally across the image and unroll | |
| 89 * the loop. | |
| 90 * (4) This runs at about 45 Mpix/sec on a 3 GHz processor. | |
| 91 * </pre> | |
| 92 */ | |
| 93 PIX * | |
| 94 pixSobelEdgeFilter(PIX *pixs, | |
| 95 l_int32 orientflag) | |
| 96 { | |
| 97 l_int32 w, h, d, i, j, wplt, wpld, gx, gy, vald; | |
| 98 l_int32 val1, val2, val3, val4, val5, val6, val7, val8, val9; | |
| 99 l_uint32 *datat, *linet, *datad, *lined; | |
| 100 PIX *pixt, *pixd; | |
| 101 | |
| 102 if (!pixs) | |
| 103 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); | |
| 104 pixGetDimensions(pixs, &w, &h, &d); | |
| 105 if (d != 8) | |
| 106 return (PIX *)ERROR_PTR("pixs not 8 bpp", __func__, NULL); | |
| 107 if (orientflag != L_HORIZONTAL_EDGES && orientflag != L_VERTICAL_EDGES && | |
| 108 orientflag != L_ALL_EDGES) | |
| 109 return (PIX *)ERROR_PTR("invalid orientflag", __func__, NULL); | |
| 110 | |
| 111 /* Add 1 pixel (mirrored) to each side of the image. */ | |
| 112 if ((pixt = pixAddMirroredBorder(pixs, 1, 1, 1, 1)) == NULL) | |
| 113 return (PIX *)ERROR_PTR("pixt not made", __func__, NULL); | |
| 114 | |
| 115 /* Compute filter output at each location. */ | |
| 116 pixd = pixCreateTemplate(pixs); | |
| 117 datat = pixGetData(pixt); | |
| 118 wplt = pixGetWpl(pixt); | |
| 119 datad = pixGetData(pixd); | |
| 120 wpld = pixGetWpl(pixd); | |
| 121 for (i = 0; i < h; i++) { | |
| 122 linet = datat + i * wplt; | |
| 123 lined = datad + i * wpld; | |
| 124 for (j = 0; j < w; j++) { | |
| 125 if (j == 0) { /* start a new row */ | |
| 126 val1 = GET_DATA_BYTE(linet, j); | |
| 127 val2 = GET_DATA_BYTE(linet + wplt, j); | |
| 128 val3 = GET_DATA_BYTE(linet + 2 * wplt, j); | |
| 129 val4 = GET_DATA_BYTE(linet, j + 1); | |
| 130 val5 = GET_DATA_BYTE(linet + wplt, j + 1); | |
| 131 val6 = GET_DATA_BYTE(linet + 2 * wplt, j + 1); | |
| 132 val7 = GET_DATA_BYTE(linet, j + 2); | |
| 133 val8 = GET_DATA_BYTE(linet + wplt, j + 2); | |
| 134 val9 = GET_DATA_BYTE(linet + 2 * wplt, j + 2); | |
| 135 } else { /* shift right by 1 pixel; update incrementally */ | |
| 136 val1 = val4; | |
| 137 val2 = val5; | |
| 138 val3 = val6; | |
| 139 val4 = val7; | |
| 140 val5 = val8; | |
| 141 val6 = val9; | |
| 142 val7 = GET_DATA_BYTE(linet, j + 2); | |
| 143 val8 = GET_DATA_BYTE(linet + wplt, j + 2); | |
| 144 val9 = GET_DATA_BYTE(linet + 2 * wplt, j + 2); | |
| 145 } | |
| 146 if (orientflag == L_HORIZONTAL_EDGES) | |
| 147 vald = L_ABS(val1 + 2 * val4 + val7 | |
| 148 - val3 - 2 * val6 - val9) >> 3; | |
| 149 else if (orientflag == L_VERTICAL_EDGES) | |
| 150 vald = L_ABS(val1 + 2 * val2 + val3 - val7 | |
| 151 - 2 * val8 - val9) >> 3; | |
| 152 else { /* L_ALL_EDGES */ | |
| 153 gx = L_ABS(val1 + 2 * val2 + val3 - val7 | |
| 154 - 2 * val8 - val9) >> 3; | |
| 155 gy = L_ABS(val1 + 2 * val4 + val7 | |
| 156 - val3 - 2 * val6 - val9) >> 3; | |
| 157 vald = L_MIN(255, gx + gy); | |
| 158 } | |
| 159 SET_DATA_BYTE(lined, j, vald); | |
| 160 } | |
| 161 } | |
| 162 | |
| 163 pixDestroy(&pixt); | |
| 164 return pixd; | |
| 165 } | |
| 166 | |
| 167 | |
| 168 /*----------------------------------------------------------------------* | |
| 169 * Two-sided edge gradient filter * | |
| 170 *----------------------------------------------------------------------*/ | |
| 171 /*! | |
| 172 * \brief pixTwoSidedEdgeFilter() | |
| 173 * | |
| 174 * \param[in] pixs 8 bpp; no colormap | |
| 175 * \param[in] orientflag L_HORIZONTAL_EDGES, L_VERTICAL_EDGES | |
| 176 * \return pixd 8 bpp, edges are brighter, or NULL on error | |
| 177 * | |
| 178 * <pre> | |
| 179 * Notes: | |
| 180 * (1) For detecting vertical edges, this considers the | |
| 181 * difference of the central pixel from those on the left | |
| 182 * and right. For situations where the gradient is the same | |
| 183 * sign on both sides, this computes and stores the minimum | |
| 184 * (absolute value of the) difference. The reason for | |
| 185 * checking the sign is that we are looking for pixels within | |
| 186 * a transition. By contrast, for single pixel noise, the pixel | |
| 187 * value is either larger than or smaller than its neighbors, | |
| 188 * so the gradient would change direction on each side. Horizontal | |
| 189 * edges are handled similarly, looking for vertical gradients. | |
| 190 * (2) To generate a binary image of the edges, threshold | |
| 191 * the result using pixThresholdToBinary(). If the high | |
| 192 * edge values are to be fg (1), invert after running | |
| 193 * pixThresholdToBinary(). | |
| 194 * (3) This runs at about 60 Mpix/sec on a 3 GHz processor. | |
| 195 * It is about 30% faster than Sobel, and the results are | |
| 196 * similar. | |
| 197 * </pre> | |
| 198 */ | |
| 199 PIX * | |
| 200 pixTwoSidedEdgeFilter(PIX *pixs, | |
| 201 l_int32 orientflag) | |
| 202 { | |
| 203 l_int32 w, h, d, i, j, wpls, wpld; | |
| 204 l_int32 cval, rval, bval, val, lgrad, rgrad, tgrad, bgrad; | |
| 205 l_uint32 *datas, *lines, *datad, *lined; | |
| 206 PIX *pixd; | |
| 207 | |
| 208 if (!pixs) | |
| 209 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); | |
| 210 pixGetDimensions(pixs, &w, &h, &d); | |
| 211 if (d != 8) | |
| 212 return (PIX *)ERROR_PTR("pixs not 8 bpp", __func__, NULL); | |
| 213 if (orientflag != L_HORIZONTAL_EDGES && orientflag != L_VERTICAL_EDGES) | |
| 214 return (PIX *)ERROR_PTR("invalid orientflag", __func__, NULL); | |
| 215 | |
| 216 pixd = pixCreateTemplate(pixs); | |
| 217 datas = pixGetData(pixs); | |
| 218 wpls = pixGetWpl(pixs); | |
| 219 datad = pixGetData(pixd); | |
| 220 wpld = pixGetWpl(pixd); | |
| 221 if (orientflag == L_VERTICAL_EDGES) { | |
| 222 for (i = 0; i < h; i++) { | |
| 223 lines = datas + i * wpls; | |
| 224 lined = datad + i * wpld; | |
| 225 cval = GET_DATA_BYTE(lines, 1); | |
| 226 lgrad = cval - GET_DATA_BYTE(lines, 0); | |
| 227 for (j = 1; j < w - 1; j++) { | |
| 228 rval = GET_DATA_BYTE(lines, j + 1); | |
| 229 rgrad = rval - cval; | |
| 230 if (lgrad * rgrad > 0) { | |
| 231 if (lgrad < 0) | |
| 232 val = -L_MAX(lgrad, rgrad); | |
| 233 else | |
| 234 val = L_MIN(lgrad, rgrad); | |
| 235 SET_DATA_BYTE(lined, j, val); | |
| 236 } | |
| 237 lgrad = rgrad; | |
| 238 cval = rval; | |
| 239 } | |
| 240 } | |
| 241 } | |
| 242 else { /* L_HORIZONTAL_EDGES) */ | |
| 243 for (j = 0; j < w; j++) { | |
| 244 lines = datas + wpls; | |
| 245 cval = GET_DATA_BYTE(lines, j); /* for line 1 */ | |
| 246 tgrad = cval - GET_DATA_BYTE(datas, j); | |
| 247 for (i = 1; i < h - 1; i++) { | |
| 248 lines += wpls; /* for line i + 1 */ | |
| 249 lined = datad + i * wpld; | |
| 250 bval = GET_DATA_BYTE(lines, j); | |
| 251 bgrad = bval - cval; | |
| 252 if (tgrad * bgrad > 0) { | |
| 253 if (tgrad < 0) | |
| 254 val = -L_MAX(tgrad, bgrad); | |
| 255 else | |
| 256 val = L_MIN(tgrad, bgrad); | |
| 257 SET_DATA_BYTE(lined, j, val); | |
| 258 } | |
| 259 tgrad = bgrad; | |
| 260 cval = bval; | |
| 261 } | |
| 262 } | |
| 263 } | |
| 264 | |
| 265 return pixd; | |
| 266 } | |
| 267 | |
| 268 | |
| 269 /*----------------------------------------------------------------------* | |
| 270 * Measurement of edge smoothness * | |
| 271 *----------------------------------------------------------------------*/ | |
| 272 /*! | |
| 273 * \brief pixMeasureEdgeSmoothness() | |
| 274 * | |
| 275 * \param[in] pixs 1 bpp | |
| 276 * \param[in] side L_FROM_LEFT, L_FROM_RIGHT, L_FROM_TOP, L_FROM_BOT | |
| 277 * \param[in] minjump minimum jump to be counted; >= 1 | |
| 278 * \param[in] minreversal minimum reversal size for new peak or valley | |
| 279 * \param[out] pjpl [optional] jumps/length: number of jumps, | |
| 280 * normalized to length of component side | |
| 281 * \param[out] pjspl [optional] jumpsum/length: sum of all | |
| 282 * sufficiently large jumps, normalized to length | |
| 283 * of component side | |
| 284 * \param[out] prpl [optional] reversals/length: number of | |
| 285 * peak-to-valley or valley-to-peak reversals, | |
| 286 * normalized to length of component side | |
| 287 * \param[in] debugfile [optional] displays constructed edge; use NULL | |
| 288 * for no output | |
| 289 * \return 0 if OK, 1 on error | |
| 290 * | |
| 291 * <pre> | |
| 292 * Notes: | |
| 293 * (1) This computes three measures of smoothness of the edge of a | |
| 294 * connected component: | |
| 295 * * jumps/length: (jpl) the number of jumps of size >= %minjump, | |
| 296 * normalized to the length of the side | |
| 297 * * jump sum/length: (jspl) the sum of all jump lengths of | |
| 298 * size >= %minjump, normalized to the length of the side | |
| 299 * * reversals/length: (rpl) the number of peak <--> valley | |
| 300 * reversals, using %minreverse as a minimum deviation of | |
| 301 * the peak or valley from its preceding extremum, | |
| 302 * normalized to the length of the side | |
| 303 * (2) The input pix should be a single connected component, but | |
| 304 * this is not required. | |
| 305 * </pre> | |
| 306 */ | |
| 307 l_ok | |
| 308 pixMeasureEdgeSmoothness(PIX *pixs, | |
| 309 l_int32 side, | |
| 310 l_int32 minjump, | |
| 311 l_int32 minreversal, | |
| 312 l_float32 *pjpl, | |
| 313 l_float32 *pjspl, | |
| 314 l_float32 *prpl, | |
| 315 const char *debugfile) | |
| 316 { | |
| 317 l_int32 i, n, val, nval, diff, njumps, jumpsum, nreversal; | |
| 318 NUMA *na, *nae; | |
| 319 | |
| 320 if (pjpl) *pjpl = 0.0; | |
| 321 if (pjspl) *pjspl = 0.0; | |
| 322 if (prpl) *prpl = 0.0; | |
| 323 if (!pjpl && !pjspl && !prpl && !debugfile) | |
| 324 return ERROR_INT("no output requested", __func__, 1); | |
| 325 if (!pixs || pixGetDepth(pixs) != 1) | |
| 326 return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1); | |
| 327 if (side != L_FROM_LEFT && side != L_FROM_RIGHT && | |
| 328 side != L_FROM_TOP && side != L_FROM_BOT) | |
| 329 return ERROR_INT("invalid side", __func__, 1); | |
| 330 if (minjump < 1) | |
| 331 return ERROR_INT("invalid minjump; must be >= 1", __func__, 1); | |
| 332 if (minreversal < 1) | |
| 333 return ERROR_INT("invalid minreversal; must be >= 1", __func__, 1); | |
| 334 | |
| 335 if ((na = pixGetEdgeProfile(pixs, side, debugfile)) == NULL) | |
| 336 return ERROR_INT("edge profile not made", __func__, 1); | |
| 337 if ((n = numaGetCount(na)) < 2) { | |
| 338 numaDestroy(&na); | |
| 339 return 0; | |
| 340 } | |
| 341 | |
| 342 if (pjpl || pjspl) { | |
| 343 jumpsum = 0; | |
| 344 njumps = 0; | |
| 345 numaGetIValue(na, 0, &val); | |
| 346 for (i = 1; i < n; i++) { | |
| 347 numaGetIValue(na, i, &nval); | |
| 348 diff = L_ABS(nval - val); | |
| 349 if (diff >= minjump) { | |
| 350 njumps++; | |
| 351 jumpsum += diff; | |
| 352 } | |
| 353 val = nval; | |
| 354 } | |
| 355 if (pjpl) | |
| 356 *pjpl = (l_float32)njumps / (l_float32)(n - 1); | |
| 357 if (pjspl) | |
| 358 *pjspl = (l_float32)jumpsum / (l_float32)(n - 1); | |
| 359 } | |
| 360 | |
| 361 if (prpl) { | |
| 362 nae = numaFindExtrema(na, minreversal, NULL); | |
| 363 nreversal = numaGetCount(nae) - 1; | |
| 364 *prpl = (l_float32)nreversal / (l_float32)(n - 1); | |
| 365 numaDestroy(&nae); | |
| 366 } | |
| 367 | |
| 368 numaDestroy(&na); | |
| 369 return 0; | |
| 370 } | |
| 371 | |
| 372 | |
| 373 /*! | |
| 374 * \brief pixGetEdgeProfile() | |
| 375 * | |
| 376 * \param[in] pixs 1 bpp | |
| 377 * \param[in] side L_FROM_LEFT, L_FROM_RIGHT, L_FROM_TOP, L_FROM_BOT | |
| 378 * \param[in] debugfile [optional] displays constructed edge; use NULL | |
| 379 * for no output | |
| 380 * \return na of fg edge pixel locations, or NULL on error | |
| 381 */ | |
| 382 NUMA * | |
| 383 pixGetEdgeProfile(PIX *pixs, | |
| 384 l_int32 side, | |
| 385 const char *debugfile) | |
| 386 { | |
| 387 l_int32 x, y, w, h, loc, index, ival; | |
| 388 l_uint32 val; | |
| 389 NUMA *na; | |
| 390 PIX *pixt; | |
| 391 PIXCMAP *cmap; | |
| 392 | |
| 393 if (!pixs || pixGetDepth(pixs) != 1) | |
| 394 return (NUMA *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL); | |
| 395 if (side != L_FROM_LEFT && side != L_FROM_RIGHT && | |
| 396 side != L_FROM_TOP && side != L_FROM_BOT) | |
| 397 return (NUMA *)ERROR_PTR("invalid side", __func__, NULL); | |
| 398 | |
| 399 pixGetDimensions(pixs, &w, &h, NULL); | |
| 400 if (side == L_FROM_LEFT || side == L_FROM_RIGHT) | |
| 401 na = numaCreate(h); | |
| 402 else | |
| 403 na = numaCreate(w); | |
| 404 if (side == L_FROM_LEFT) { | |
| 405 pixGetLastOffPixelInRun(pixs, 0, 0, L_FROM_LEFT, &loc); | |
| 406 loc = (loc == w - 1) ? 0 : loc + 1; /* back to the left edge */ | |
| 407 numaAddNumber(na, loc); | |
| 408 for (y = 1; y < h; y++) { | |
| 409 pixGetPixel(pixs, loc, y, &val); | |
| 410 if (val == 1) { | |
| 411 pixGetLastOnPixelInRun(pixs, loc, y, L_FROM_RIGHT, &loc); | |
| 412 } else { | |
| 413 pixGetLastOffPixelInRun(pixs, loc, y, L_FROM_LEFT, &loc); | |
| 414 loc = (loc == w - 1) ? 0 : loc + 1; | |
| 415 } | |
| 416 numaAddNumber(na, loc); | |
| 417 } | |
| 418 } | |
| 419 else if (side == L_FROM_RIGHT) { | |
| 420 pixGetLastOffPixelInRun(pixs, w - 1, 0, L_FROM_RIGHT, &loc); | |
| 421 loc = (loc == 0) ? w - 1 : loc - 1; /* back to the right edge */ | |
| 422 numaAddNumber(na, loc); | |
| 423 for (y = 1; y < h; y++) { | |
| 424 pixGetPixel(pixs, loc, y, &val); | |
| 425 if (val == 1) { | |
| 426 pixGetLastOnPixelInRun(pixs, loc, y, L_FROM_LEFT, &loc); | |
| 427 } else { | |
| 428 pixGetLastOffPixelInRun(pixs, loc, y, L_FROM_RIGHT, &loc); | |
| 429 loc = (loc == 0) ? w - 1 : loc - 1; | |
| 430 } | |
| 431 numaAddNumber(na, loc); | |
| 432 } | |
| 433 } | |
| 434 else if (side == L_FROM_TOP) { | |
| 435 pixGetLastOffPixelInRun(pixs, 0, 0, L_FROM_TOP, &loc); | |
| 436 loc = (loc == h - 1) ? 0 : loc + 1; /* back to the top edge */ | |
| 437 numaAddNumber(na, loc); | |
| 438 for (x = 1; x < w; x++) { | |
| 439 pixGetPixel(pixs, x, loc, &val); | |
| 440 if (val == 1) { | |
| 441 pixGetLastOnPixelInRun(pixs, x, loc, L_FROM_BOT, &loc); | |
| 442 } else { | |
| 443 pixGetLastOffPixelInRun(pixs, x, loc, L_FROM_TOP, &loc); | |
| 444 loc = (loc == h - 1) ? 0 : loc + 1; | |
| 445 } | |
| 446 numaAddNumber(na, loc); | |
| 447 } | |
| 448 } | |
| 449 else { /* side == L_FROM_BOT */ | |
| 450 pixGetLastOffPixelInRun(pixs, 0, h - 1, L_FROM_BOT, &loc); | |
| 451 loc = (loc == 0) ? h - 1 : loc - 1; /* back to the bottom edge */ | |
| 452 numaAddNumber(na, loc); | |
| 453 for (x = 1; x < w; x++) { | |
| 454 pixGetPixel(pixs, x, loc, &val); | |
| 455 if (val == 1) { | |
| 456 pixGetLastOnPixelInRun(pixs, x, loc, L_FROM_TOP, &loc); | |
| 457 } else { | |
| 458 pixGetLastOffPixelInRun(pixs, x, loc, L_FROM_BOT, &loc); | |
| 459 loc = (loc == 0) ? h - 1 : loc - 1; | |
| 460 } | |
| 461 numaAddNumber(na, loc); | |
| 462 } | |
| 463 } | |
| 464 | |
| 465 if (debugfile) { | |
| 466 pixt = pixConvertTo8(pixs, TRUE); | |
| 467 cmap = pixGetColormap(pixt); | |
| 468 pixcmapAddColor(cmap, 255, 0, 0); | |
| 469 index = pixcmapGetCount(cmap) - 1; | |
| 470 if (side == L_FROM_LEFT || side == L_FROM_RIGHT) { | |
| 471 for (y = 0; y < h; y++) { | |
| 472 numaGetIValue(na, y, &ival); | |
| 473 pixSetPixel(pixt, ival, y, index); | |
| 474 } | |
| 475 } else { /* L_FROM_TOP or L_FROM_BOT */ | |
| 476 for (x = 0; x < w; x++) { | |
| 477 numaGetIValue(na, x, &ival); | |
| 478 pixSetPixel(pixt, x, ival, index); | |
| 479 } | |
| 480 } | |
| 481 pixWrite(debugfile, pixt, IFF_PNG); | |
| 482 pixDestroy(&pixt); | |
| 483 } | |
| 484 | |
| 485 return na; | |
| 486 } | |
| 487 | |
| 488 | |
| 489 /* | |
| 490 * \brief pixGetLastOffPixelInRun() | |
| 491 * | |
| 492 * \param[in] pixs 1 bpp | |
| 493 * \param[in] x, y starting location | |
| 494 * \param[in] direction L_FROM_LEFT, L_FROM_RIGHT, L_FROM_TOP, L_FROM_BOT | |
| 495 * \param[out] ploc location in scan direction coordinate | |
| 496 * of last OFF pixel found | |
| 497 * \return 0 if OK, 1 on error | |
| 498 * | |
| 499 * <pre> | |
| 500 * Notes: | |
| 501 * (1) Search starts from the pixel at (x, y), which is OFF. | |
| 502 * (2) It returns the location in the scan direction of the last | |
| 503 * pixel in the current run that is OFF. | |
| 504 * (3) The interface for these pixel run functions is cleaner when | |
| 505 * you ask for the last pixel in the current run, rather than the | |
| 506 * first pixel of opposite polarity that is found, because the | |
| 507 * current run may go to the edge of the image, in which case | |
| 508 * no pixel of opposite polarity is found. | |
| 509 * </pre> | |
| 510 */ | |
| 511 l_ok | |
| 512 pixGetLastOffPixelInRun(PIX *pixs, | |
| 513 l_int32 x, | |
| 514 l_int32 y, | |
| 515 l_int32 direction, | |
| 516 l_int32 *ploc) | |
| 517 { | |
| 518 l_int32 loc, w, h; | |
| 519 l_uint32 val; | |
| 520 | |
| 521 if (!ploc) | |
| 522 return ERROR_INT("&loc not defined", __func__, 1); | |
| 523 *ploc = 0; | |
| 524 if (!pixs || pixGetDepth(pixs) != 1) | |
| 525 return ERROR_INT("pixs undefined or not 1 bpp", __func__, 1); | |
| 526 if (direction != L_FROM_LEFT && direction != L_FROM_RIGHT && | |
| 527 direction != L_FROM_TOP && direction != L_FROM_BOT) | |
| 528 return ERROR_INT("invalid side", __func__, 1); | |
| 529 | |
| 530 pixGetDimensions(pixs, &w, &h, NULL); | |
| 531 if (direction == L_FROM_LEFT) { | |
| 532 for (loc = x; loc < w; loc++) { | |
| 533 pixGetPixel(pixs, loc, y, &val); | |
| 534 if (val == 1) | |
| 535 break; | |
| 536 } | |
| 537 *ploc = loc - 1; | |
| 538 } else if (direction == L_FROM_RIGHT) { | |
| 539 for (loc = x; loc >= 0; loc--) { | |
| 540 pixGetPixel(pixs, loc, y, &val); | |
| 541 if (val == 1) | |
| 542 break; | |
| 543 } | |
| 544 *ploc = loc + 1; | |
| 545 } | |
| 546 else if (direction == L_FROM_TOP) { | |
| 547 for (loc = y; loc < h; loc++) { | |
| 548 pixGetPixel(pixs, x, loc, &val); | |
| 549 if (val == 1) | |
| 550 break; | |
| 551 } | |
| 552 *ploc = loc - 1; | |
| 553 } | |
| 554 else if (direction == L_FROM_BOT) { | |
| 555 for (loc = y; loc >= 0; loc--) { | |
| 556 pixGetPixel(pixs, x, loc, &val); | |
| 557 if (val == 1) | |
| 558 break; | |
| 559 } | |
| 560 *ploc = loc + 1; | |
| 561 } | |
| 562 return 0; | |
| 563 } | |
| 564 | |
| 565 | |
| 566 /* | |
| 567 * \brief pixGetLastOnPixelInRun() | |
| 568 * | |
| 569 * \param[in] pixs 1 bpp | |
| 570 * \param[in] x, y starting location | |
| 571 * \param[in] direction L_FROM_LEFT, L_FROM_RIGHT, L_FROM_TOP, L_FROM_BOT | |
| 572 * \param[out] ploc location in scan direction coordinate | |
| 573 * of first ON pixel found | |
| 574 * \return 0 if OK, 1 on error | |
| 575 * | |
| 576 * <pre> | |
| 577 * Notes: | |
| 578 * (1) Search starts from the pixel at (x, y), which is ON. | |
| 579 * (2) It returns the location in the scan direction of the last | |
| 580 * pixel in the current run that is ON. | |
| 581 * </pre> | |
| 582 */ | |
| 583 l_int32 | |
| 584 pixGetLastOnPixelInRun(PIX *pixs, | |
| 585 l_int32 x, | |
| 586 l_int32 y, | |
| 587 l_int32 direction, | |
| 588 l_int32 *ploc) | |
| 589 { | |
| 590 l_int32 loc, w, h; | |
| 591 l_uint32 val; | |
| 592 | |
| 593 if (!ploc) | |
| 594 return ERROR_INT("&loc not defined", __func__, 1); | |
| 595 *ploc = 0; | |
| 596 if (!pixs || pixGetDepth(pixs) != 1) | |
| 597 return ERROR_INT("pixs undefined or not 1 bpp", __func__, 1); | |
| 598 if (direction != L_FROM_LEFT && direction != L_FROM_RIGHT && | |
| 599 direction != L_FROM_TOP && direction != L_FROM_BOT) | |
| 600 return ERROR_INT("invalid side", __func__, 1); | |
| 601 | |
| 602 pixGetDimensions(pixs, &w, &h, NULL); | |
| 603 if (direction == L_FROM_LEFT) { | |
| 604 for (loc = x; loc < w; loc++) { | |
| 605 pixGetPixel(pixs, loc, y, &val); | |
| 606 if (val == 0) | |
| 607 break; | |
| 608 } | |
| 609 *ploc = loc - 1; | |
| 610 } else if (direction == L_FROM_RIGHT) { | |
| 611 for (loc = x; loc >= 0; loc--) { | |
| 612 pixGetPixel(pixs, loc, y, &val); | |
| 613 if (val == 0) | |
| 614 break; | |
| 615 } | |
| 616 *ploc = loc + 1; | |
| 617 } | |
| 618 else if (direction == L_FROM_TOP) { | |
| 619 for (loc = y; loc < h; loc++) { | |
| 620 pixGetPixel(pixs, x, loc, &val); | |
| 621 if (val == 0) | |
| 622 break; | |
| 623 } | |
| 624 *ploc = loc - 1; | |
| 625 } | |
| 626 else if (direction == L_FROM_BOT) { | |
| 627 for (loc = y; loc >= 0; loc--) { | |
| 628 pixGetPixel(pixs, x, loc, &val); | |
| 629 if (val == 0) | |
| 630 break; | |
| 631 } | |
| 632 *ploc = loc + 1; | |
| 633 } | |
| 634 return 0; | |
| 635 } |
