/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 | } |