Coverage Report

Created: 2024-07-27 06:28

/src/leptonica/src/edge.c
Line
Count
Source (jump to first uncovered line)
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
0
{
97
0
l_int32    w, h, d, i, j, wplt, wpld, gx, gy, vald;
98
0
l_int32    val1, val2, val3, val4, val5, val6, val7, val8, val9;
99
0
l_uint32  *datat, *linet, *datad, *lined;
100
0
PIX       *pixt, *pixd;
101
102
0
    if (!pixs)
103
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
104
0
    pixGetDimensions(pixs, &w, &h, &d);
105
0
    if (d != 8)
106
0
        return (PIX *)ERROR_PTR("pixs not 8 bpp", __func__, NULL);
107
0
    if (orientflag != L_HORIZONTAL_EDGES && orientflag != L_VERTICAL_EDGES &&
108
0
        orientflag != L_ALL_EDGES)
109
0
        return (PIX *)ERROR_PTR("invalid orientflag", __func__, NULL);
110
111
        /* Add 1 pixel (mirrored) to each side of the image. */
112
0
    if ((pixt = pixAddMirroredBorder(pixs, 1, 1, 1, 1)) == NULL)
113
0
        return (PIX *)ERROR_PTR("pixt not made", __func__, NULL);
114
115
        /* Compute filter output at each location. */
116
0
    pixd = pixCreateTemplate(pixs);
117
0
    datat = pixGetData(pixt);
118
0
    wplt = pixGetWpl(pixt);
119
0
    datad = pixGetData(pixd);
120
0
    wpld = pixGetWpl(pixd);
121
0
    for (i = 0; i < h; i++) {
122
0
        linet = datat + i * wplt;
123
0
        lined = datad + i * wpld;
124
0
        for (j = 0; j < w; j++) {
125
0
            if (j == 0) {  /* start a new row */
126
0
                val1 = GET_DATA_BYTE(linet, j);
127
0
                val2 = GET_DATA_BYTE(linet + wplt, j);
128
0
                val3 = GET_DATA_BYTE(linet + 2 * wplt, j);
129
0
                val4 = GET_DATA_BYTE(linet, j + 1);
130
0
                val5 = GET_DATA_BYTE(linet + wplt, j + 1);
131
0
                val6 = GET_DATA_BYTE(linet + 2 * wplt, j + 1);
132
0
                val7 = GET_DATA_BYTE(linet, j + 2);
133
0
                val8 = GET_DATA_BYTE(linet + wplt, j + 2);
134
0
                val9 = GET_DATA_BYTE(linet + 2 * wplt, j + 2);
135
0
            } else {  /* shift right by 1 pixel; update incrementally */
136
0
                val1 = val4;
137
0
                val2 = val5;
138
0
                val3 = val6;
139
0
                val4 = val7;
140
0
                val5 = val8;
141
0
                val6 = val9;
142
0
                val7 = GET_DATA_BYTE(linet, j + 2);
143
0
                val8 = GET_DATA_BYTE(linet + wplt, j + 2);
144
0
                val9 = GET_DATA_BYTE(linet + 2 * wplt, j + 2);
145
0
            }
146
0
            if (orientflag == L_HORIZONTAL_EDGES)
147
0
                vald = L_ABS(val1 + 2 * val4 + val7
148
0
                             - val3 - 2 * val6 - val9) >> 3;
149
0
            else if (orientflag == L_VERTICAL_EDGES)
150
0
                vald = L_ABS(val1 + 2 * val2 + val3 - val7
151
0
                             - 2 * val8 - val9) >> 3;
152
0
            else {  /* L_ALL_EDGES */
153
0
                gx = L_ABS(val1 + 2 * val2 + val3 - val7
154
0
                           - 2 * val8 - val9) >> 3;
155
0
                gy = L_ABS(val1 + 2 * val4 + val7
156
0
                             - val3 - 2 * val6 - val9) >> 3;
157
0
                vald = L_MIN(255, gx + gy);
158
0
            }
159
0
            SET_DATA_BYTE(lined, j, vald);
160
0
        }
161
0
    }
162
163
0
    pixDestroy(&pixt);
164
0
    return pixd;
165
0
}
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
0
{
203
0
l_int32    w, h, d, i, j, wpls, wpld;
204
0
l_int32    cval, rval, bval, val, lgrad, rgrad, tgrad, bgrad;
205
0
l_uint32  *datas, *lines, *datad, *lined;
206
0
PIX       *pixd;
207
208
0
    if (!pixs)
209
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
210
0
    pixGetDimensions(pixs, &w, &h, &d);
211
0
    if (d != 8)
212
0
        return (PIX *)ERROR_PTR("pixs not 8 bpp", __func__, NULL);
213
0
    if (orientflag != L_HORIZONTAL_EDGES && orientflag != L_VERTICAL_EDGES)
214
0
        return (PIX *)ERROR_PTR("invalid orientflag", __func__, NULL);
215
216
0
    pixd = pixCreateTemplate(pixs);
217
0
    datas = pixGetData(pixs);
218
0
    wpls = pixGetWpl(pixs);
219
0
    datad = pixGetData(pixd);
220
0
    wpld = pixGetWpl(pixd);
221
0
    if (orientflag == L_VERTICAL_EDGES) {
222
0
        for (i = 0; i < h; i++) {
223
0
            lines = datas + i * wpls;
224
0
            lined = datad + i * wpld;
225
0
            cval = GET_DATA_BYTE(lines, 1);
226
0
            lgrad = cval - GET_DATA_BYTE(lines, 0);
227
0
            for (j = 1; j < w - 1; j++) {
228
0
                rval = GET_DATA_BYTE(lines, j + 1);
229
0
                rgrad = rval - cval;
230
0
                if (lgrad * rgrad > 0) {
231
0
                    if (lgrad < 0)
232
0
                        val = -L_MAX(lgrad, rgrad);
233
0
                    else
234
0
                        val = L_MIN(lgrad, rgrad);
235
0
                    SET_DATA_BYTE(lined, j, val);
236
0
                }
237
0
                lgrad = rgrad;
238
0
                cval = rval;
239
0
            }
240
0
        }
241
0
    }
242
0
    else {  /* L_HORIZONTAL_EDGES) */
243
0
        for (j = 0; j < w; j++) {
244
0
            lines = datas + wpls;
245
0
            cval = GET_DATA_BYTE(lines, j);  /* for line 1 */
246
0
            tgrad = cval - GET_DATA_BYTE(datas, j);
247
0
            for (i = 1; i < h - 1; i++) {
248
0
                lines += wpls;  /* for line i + 1 */
249
0
                lined = datad + i * wpld;
250
0
                bval = GET_DATA_BYTE(lines, j);
251
0
                bgrad = bval - cval;
252
0
                if (tgrad * bgrad > 0) {
253
0
                    if (tgrad < 0)
254
0
                        val = -L_MAX(tgrad, bgrad);
255
0
                    else
256
0
                        val = L_MIN(tgrad, bgrad);
257
0
                    SET_DATA_BYTE(lined, j, val);
258
0
                }
259
0
                tgrad = bgrad;
260
0
                cval = bval;
261
0
            }
262
0
        }
263
0
    }
264
265
0
    return pixd;
266
0
}
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
0
{
317
0
l_int32  i, n, val, nval, diff, njumps, jumpsum, nreversal;
318
0
NUMA    *na, *nae;
319
320
0
    if (pjpl) *pjpl = 0.0;
321
0
    if (pjspl) *pjspl = 0.0;
322
0
    if (prpl) *prpl = 0.0;
323
0
    if (!pjpl && !pjspl && !prpl && !debugfile)
324
0
        return ERROR_INT("no output requested", __func__, 1);
325
0
    if (!pixs || pixGetDepth(pixs) != 1)
326
0
        return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1);
327
0
    if (side != L_FROM_LEFT && side != L_FROM_RIGHT &&
328
0
        side != L_FROM_TOP && side != L_FROM_BOT)
329
0
        return ERROR_INT("invalid side", __func__, 1);
330
0
    if (minjump < 1)
331
0
        return ERROR_INT("invalid minjump; must be >= 1", __func__, 1);
332
0
    if (minreversal < 1)
333
0
        return ERROR_INT("invalid minreversal; must be >= 1", __func__, 1);
334
335
0
    if ((na = pixGetEdgeProfile(pixs, side, debugfile)) == NULL)
336
0
        return ERROR_INT("edge profile not made", __func__, 1);
337
0
    if ((n = numaGetCount(na)) < 2) {
338
0
        numaDestroy(&na);
339
0
        return 0;
340
0
    }
341
342
0
    if (pjpl || pjspl) {
343
0
        jumpsum = 0;
344
0
        njumps = 0;
345
0
        numaGetIValue(na, 0, &val);
346
0
        for (i = 1; i < n; i++) {
347
0
            numaGetIValue(na, i, &nval);
348
0
            diff = L_ABS(nval - val);
349
0
            if (diff >= minjump) {
350
0
                njumps++;
351
0
                jumpsum += diff;
352
0
            }
353
0
            val = nval;
354
0
        }
355
0
        if (pjpl)
356
0
            *pjpl = (l_float32)njumps / (l_float32)(n - 1);
357
0
        if (pjspl)
358
0
            *pjspl = (l_float32)jumpsum / (l_float32)(n - 1);
359
0
    }
360
361
0
    if (prpl) {
362
0
        nae = numaFindExtrema(na, minreversal, NULL);
363
0
        nreversal = numaGetCount(nae) - 1;
364
0
        *prpl = (l_float32)nreversal / (l_float32)(n - 1);
365
0
        numaDestroy(&nae);
366
0
    }
367
368
0
    numaDestroy(&na);
369
0
    return 0;
370
0
}
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
0
{
387
0
l_int32   x, y, w, h, loc, index, ival;
388
0
l_uint32  val;
389
0
NUMA     *na;
390
0
PIX      *pixt;
391
0
PIXCMAP  *cmap;
392
393
0
    if (!pixs || pixGetDepth(pixs) != 1)
394
0
        return (NUMA *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL);
395
0
    if (side != L_FROM_LEFT && side != L_FROM_RIGHT &&
396
0
        side != L_FROM_TOP && side != L_FROM_BOT)
397
0
        return (NUMA *)ERROR_PTR("invalid side", __func__, NULL);
398
399
0
    pixGetDimensions(pixs, &w, &h, NULL);
400
0
    if (side == L_FROM_LEFT || side == L_FROM_RIGHT)
401
0
        na = numaCreate(h);
402
0
    else
403
0
        na = numaCreate(w);
404
0
    if (side == L_FROM_LEFT) {
405
0
        pixGetLastOffPixelInRun(pixs, 0, 0, L_FROM_LEFT, &loc);
406
0
        loc = (loc == w - 1) ? 0 : loc + 1;  /* back to the left edge */
407
0
        numaAddNumber(na, loc);
408
0
        for (y = 1; y < h; y++) {
409
0
            pixGetPixel(pixs, loc, y, &val);
410
0
            if (val == 1) {
411
0
                pixGetLastOnPixelInRun(pixs, loc, y, L_FROM_RIGHT, &loc);
412
0
            } else {
413
0
                pixGetLastOffPixelInRun(pixs, loc, y, L_FROM_LEFT, &loc);
414
0
                loc = (loc == w - 1) ? 0 : loc + 1;
415
0
            }
416
0
            numaAddNumber(na, loc);
417
0
        }
418
0
    }
419
0
    else if (side == L_FROM_RIGHT) {
420
0
        pixGetLastOffPixelInRun(pixs, w - 1, 0, L_FROM_RIGHT, &loc);
421
0
        loc = (loc == 0) ? w - 1 : loc - 1;  /* back to the right edge */
422
0
        numaAddNumber(na, loc);
423
0
        for (y = 1; y < h; y++) {
424
0
            pixGetPixel(pixs, loc, y, &val);
425
0
            if (val == 1) {
426
0
                pixGetLastOnPixelInRun(pixs, loc, y, L_FROM_LEFT, &loc);
427
0
            } else {
428
0
                pixGetLastOffPixelInRun(pixs, loc, y, L_FROM_RIGHT, &loc);
429
0
                loc = (loc == 0) ? w - 1 : loc - 1;
430
0
            }
431
0
            numaAddNumber(na, loc);
432
0
        }
433
0
    }
434
0
    else if (side == L_FROM_TOP) {
435
0
        pixGetLastOffPixelInRun(pixs, 0, 0, L_FROM_TOP, &loc);
436
0
        loc = (loc == h - 1) ? 0 : loc + 1;  /* back to the top edge */
437
0
        numaAddNumber(na, loc);
438
0
        for (x = 1; x < w; x++) {
439
0
            pixGetPixel(pixs, x, loc, &val);
440
0
            if (val == 1) {
441
0
                pixGetLastOnPixelInRun(pixs, x, loc, L_FROM_BOT, &loc);
442
0
            } else {
443
0
                pixGetLastOffPixelInRun(pixs, x, loc, L_FROM_TOP, &loc);
444
0
                loc = (loc == h - 1) ? 0 : loc + 1;
445
0
            }
446
0
            numaAddNumber(na, loc);
447
0
        }
448
0
    }
449
0
    else {  /* side == L_FROM_BOT */
450
0
        pixGetLastOffPixelInRun(pixs, 0, h - 1, L_FROM_BOT, &loc);
451
0
        loc = (loc == 0) ? h - 1 : loc - 1;  /* back to the bottom edge */
452
0
        numaAddNumber(na, loc);
453
0
        for (x = 1; x < w; x++) {
454
0
            pixGetPixel(pixs, x, loc, &val);
455
0
            if (val == 1) {
456
0
                pixGetLastOnPixelInRun(pixs, x, loc, L_FROM_TOP, &loc);
457
0
            } else {
458
0
                pixGetLastOffPixelInRun(pixs, x, loc, L_FROM_BOT, &loc);
459
0
                loc = (loc == 0) ? h - 1 : loc - 1;
460
0
            }
461
0
            numaAddNumber(na, loc);
462
0
        }
463
0
    }
464
465
0
    if (debugfile) {
466
0
        pixt = pixConvertTo8(pixs, TRUE);
467
0
        cmap = pixGetColormap(pixt);
468
0
        pixcmapAddColor(cmap, 255, 0, 0);
469
0
        index = pixcmapGetCount(cmap) - 1;
470
0
        if (side == L_FROM_LEFT || side == L_FROM_RIGHT) {
471
0
            for (y = 0; y < h; y++) {
472
0
                numaGetIValue(na, y, &ival);
473
0
                pixSetPixel(pixt, ival, y, index);
474
0
            }
475
0
        } else {  /* L_FROM_TOP or L_FROM_BOT */
476
0
            for (x = 0; x < w; x++) {
477
0
                numaGetIValue(na, x, &ival);
478
0
                pixSetPixel(pixt, x, ival, index);
479
0
            }
480
0
        }
481
0
        pixWrite(debugfile, pixt, IFF_PNG);
482
0
        pixDestroy(&pixt);
483
0
    }
484
485
0
    return na;
486
0
}
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
0
{
518
0
l_int32   loc, w, h;
519
0
l_uint32  val;
520
521
0
    if (!ploc)
522
0
        return ERROR_INT("&loc not defined", __func__, 1);
523
0
    *ploc = 0;
524
0
    if (!pixs || pixGetDepth(pixs) != 1)
525
0
        return ERROR_INT("pixs undefined or not 1 bpp", __func__, 1);
526
0
    if (direction != L_FROM_LEFT && direction != L_FROM_RIGHT &&
527
0
        direction != L_FROM_TOP && direction != L_FROM_BOT)
528
0
        return ERROR_INT("invalid side", __func__, 1);
529
530
0
    pixGetDimensions(pixs, &w, &h, NULL);
531
0
    if (direction == L_FROM_LEFT) {
532
0
        for (loc = x; loc < w; loc++) {
533
0
            pixGetPixel(pixs, loc, y, &val);
534
0
            if (val == 1)
535
0
                break;
536
0
        }
537
0
        *ploc = loc - 1;
538
0
    } else if (direction == L_FROM_RIGHT) {
539
0
        for (loc = x; loc >= 0; loc--) {
540
0
            pixGetPixel(pixs, loc, y, &val);
541
0
            if (val == 1)
542
0
                break;
543
0
        }
544
0
        *ploc = loc + 1;
545
0
    }
546
0
    else if (direction == L_FROM_TOP) {
547
0
        for (loc = y; loc < h; loc++) {
548
0
            pixGetPixel(pixs, x, loc, &val);
549
0
            if (val == 1)
550
0
                break;
551
0
        }
552
0
        *ploc = loc - 1;
553
0
    }
554
0
    else if (direction == L_FROM_BOT) {
555
0
        for (loc = y; loc >= 0; loc--) {
556
0
            pixGetPixel(pixs, x, loc, &val);
557
0
            if (val == 1)
558
0
                break;
559
0
        }
560
0
        *ploc = loc + 1;
561
0
    }
562
0
    return 0;
563
0
}
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
0
{
590
0
l_int32   loc, w, h;
591
0
l_uint32  val;
592
593
0
    if (!ploc)
594
0
        return ERROR_INT("&loc not defined", __func__, 1);
595
0
    *ploc = 0;
596
0
    if (!pixs || pixGetDepth(pixs) != 1)
597
0
        return ERROR_INT("pixs undefined or not 1 bpp", __func__, 1);
598
0
    if (direction != L_FROM_LEFT && direction != L_FROM_RIGHT &&
599
0
        direction != L_FROM_TOP && direction != L_FROM_BOT)
600
0
        return ERROR_INT("invalid side", __func__, 1);
601
602
0
    pixGetDimensions(pixs, &w, &h, NULL);
603
0
    if (direction == L_FROM_LEFT) {
604
0
        for (loc = x; loc < w; loc++) {
605
0
            pixGetPixel(pixs, loc, y, &val);
606
0
            if (val == 0)
607
0
                break;
608
0
        }
609
0
        *ploc = loc - 1;
610
0
    } else if (direction == L_FROM_RIGHT) {
611
0
        for (loc = x; loc >= 0; loc--) {
612
0
            pixGetPixel(pixs, loc, y, &val);
613
0
            if (val == 0)
614
0
                break;
615
0
        }
616
0
        *ploc = loc + 1;
617
0
    }
618
0
    else if (direction == L_FROM_TOP) {
619
0
        for (loc = y; loc < h; loc++) {
620
0
            pixGetPixel(pixs, x, loc, &val);
621
0
            if (val == 0)
622
0
                break;
623
0
        }
624
0
        *ploc = loc - 1;
625
0
    }
626
0
    else if (direction == L_FROM_BOT) {
627
0
        for (loc = y; loc >= 0; loc--) {
628
0
            pixGetPixel(pixs, x, loc, &val);
629
0
            if (val == 0)
630
0
                break;
631
0
        }
632
0
        *ploc = loc + 1;
633
0
    }
634
0
    return 0;
635
0
}