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 }