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