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