1 /*M///////////////////////////////////////////////////////////////////////////////////////
2 //
3 // IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
4 //
5 // By downloading, copying, installing or using the software you agree to this license.
6 // If you do not agree to this license, do not download, install,
7 // copy or use the software.
8 //
9 //
10 // Intel License Agreement
11 // For Open Source Computer Vision Library
12 //
13 // Copyright (C) 2000, Intel Corporation, all rights reserved.
14 // Third party copyrights are property of their respective owners.
15 //
16 // Redistribution and use in source and binary forms, with or without modification,
17 // are permitted provided that the following conditions are met:
18 //
19 // * Redistribution's of source code must retain the above copyright notice,
20 // this list of conditions and the following disclaimer.
21 //
22 // * Redistribution's in binary form must reproduce the above copyright notice,
23 // this list of conditions and the following disclaimer in the documentation
24 // and/or other materials provided with the distribution.
25 //
26 // * The name of Intel Corporation may not be used to endorse or promote products
27 // derived from this software without specific prior written permission.
28 //
29 // This software is provided by the copyright holders and contributors "as is" and
30 // any express or implied warranties, including, but not limited to, the implied
31 // warranties of merchantability and fitness for a particular purpose are disclaimed.
32 // In no event shall the Intel Corporation or contributors be liable for any direct,
33 // indirect, incidental, special, exemplary, or consequential damages
34 // (including, but not limited to, procurement of substitute goods or services;
35 // loss of use, data, or profits; or business interruption) however caused
36 // and on any theory of liability, whether in contract, strict liability,
37 // or tort (including negligence or otherwise) arising in any way out of
38 // the use of this software, even if advised of the possibility of such damage.
39 //
40 //M*/
41
42 #include "_cv.h"
43 #include <limits.h>
44 #include <stdio.h>
45
46 #define IPCV_MORPHOLOGY_PTRS( morphtype, flavor ) \
47 icv##morphtype##Rect_##flavor##_C1R_t \
48 icv##morphtype##Rect_##flavor##_C1R_p = 0; \
49 icv##morphtype##Rect_GetBufSize_##flavor##_C1R_t \
50 icv##morphtype##Rect_GetBufSize_##flavor##_C1R_p = 0; \
51 icv##morphtype##Rect_##flavor##_C3R_t \
52 icv##morphtype##Rect_##flavor##_C3R_p = 0; \
53 icv##morphtype##Rect_GetBufSize_##flavor##_C3R_t \
54 icv##morphtype##Rect_GetBufSize_##flavor##_C3R_p = 0; \
55 icv##morphtype##Rect_##flavor##_C4R_t \
56 icv##morphtype##Rect_##flavor##_C4R_p = 0; \
57 icv##morphtype##Rect_GetBufSize_##flavor##_C4R_t \
58 icv##morphtype##Rect_GetBufSize_##flavor##_C4R_p = 0; \
59 \
60 icv##morphtype##_##flavor##_C1R_t \
61 icv##morphtype##_##flavor##_C1R_p = 0; \
62 icv##morphtype##_##flavor##_C3R_t \
63 icv##morphtype##_##flavor##_C3R_p = 0; \
64 icv##morphtype##_##flavor##_C4R_t \
65 icv##morphtype##_##flavor##_C4R_p = 0;
66
67 #define IPCV_MORPHOLOGY_INITALLOC_PTRS( flavor ) \
68 icvMorphInitAlloc_##flavor##_C1R_t \
69 icvMorphInitAlloc_##flavor##_C1R_p = 0; \
70 icvMorphInitAlloc_##flavor##_C3R_t \
71 icvMorphInitAlloc_##flavor##_C3R_p = 0; \
72 icvMorphInitAlloc_##flavor##_C4R_t \
73 icvMorphInitAlloc_##flavor##_C4R_p = 0;
74
75 IPCV_MORPHOLOGY_PTRS( Erode, 8u )
76 IPCV_MORPHOLOGY_PTRS( Erode, 16u )
77 IPCV_MORPHOLOGY_PTRS( Erode, 32f )
78 IPCV_MORPHOLOGY_PTRS( Dilate, 8u )
79 IPCV_MORPHOLOGY_PTRS( Dilate, 16u )
80 IPCV_MORPHOLOGY_PTRS( Dilate, 32f )
81 IPCV_MORPHOLOGY_INITALLOC_PTRS( 8u )
82 IPCV_MORPHOLOGY_INITALLOC_PTRS( 16u )
83 IPCV_MORPHOLOGY_INITALLOC_PTRS( 32f )
84
85 icvMorphFree_t icvMorphFree_p = 0;
86
87 /****************************************************************************************\
88 Basic Morphological Operations: Erosion & Dilation
89 \****************************************************************************************/
90
91 static void icvErodeRectRow_8u( const uchar* src, uchar* dst, void* params );
92 static void icvErodeRectRow_16u( const ushort* src, ushort* dst, void* params );
93 static void icvErodeRectRow_32f( const int* src, int* dst, void* params );
94 static void icvDilateRectRow_8u( const uchar* src, uchar* dst, void* params );
95 static void icvDilateRectRow_16u( const ushort* src, ushort* dst, void* params );
96 static void icvDilateRectRow_32f( const int* src, int* dst, void* params );
97
98 static void icvErodeRectCol_8u( const uchar** src, uchar* dst, int dst_step,
99 int count, void* params );
100 static void icvErodeRectCol_16u( const ushort** src, ushort* dst, int dst_step,
101 int count, void* params );
102 static void icvErodeRectCol_32f( const int** src, int* dst, int dst_step,
103 int count, void* params );
104 static void icvDilateRectCol_8u( const uchar** src, uchar* dst, int dst_step,
105 int count, void* params );
106 static void icvDilateRectCol_16u( const ushort** src, ushort* dst, int dst_step,
107 int count, void* params );
108 static void icvDilateRectCol_32f( const int** src, int* dst, int dst_step,
109 int count, void* params );
110
111 static void icvErodeAny_8u( const uchar** src, uchar* dst, int dst_step,
112 int count, void* params );
113 static void icvErodeAny_16u( const ushort** src, ushort* dst, int dst_step,
114 int count, void* params );
115 static void icvErodeAny_32f( const int** src, int* dst, int dst_step,
116 int count, void* params );
117 static void icvDilateAny_8u( const uchar** src, uchar* dst, int dst_step,
118 int count, void* params );
119 static void icvDilateAny_16u( const ushort** src, ushort* dst, int dst_step,
120 int count, void* params );
121 static void icvDilateAny_32f( const int** src, int* dst, int dst_step,
122 int count, void* params );
123
CvMorphology()124 CvMorphology::CvMorphology()
125 {
126 element = 0;
127 el_sparse = 0;
128 }
129
CvMorphology(int _operation,int _max_width,int _src_dst_type,int _element_shape,CvMat * _element,CvSize _ksize,CvPoint _anchor,int _border_mode,CvScalar _border_value)130 CvMorphology::CvMorphology( int _operation, int _max_width, int _src_dst_type,
131 int _element_shape, CvMat* _element,
132 CvSize _ksize, CvPoint _anchor,
133 int _border_mode, CvScalar _border_value )
134 {
135 element = 0;
136 el_sparse = 0;
137 init( _operation, _max_width, _src_dst_type,
138 _element_shape, _element, _ksize, _anchor,
139 _border_mode, _border_value );
140 }
141
142
clear()143 void CvMorphology::clear()
144 {
145 cvReleaseMat( &element );
146 cvFree( &el_sparse );
147 CvBaseImageFilter::clear();
148 }
149
150
~CvMorphology()151 CvMorphology::~CvMorphology()
152 {
153 clear();
154 }
155
156
init(int _operation,int _max_width,int _src_dst_type,int _element_shape,CvMat * _element,CvSize _ksize,CvPoint _anchor,int _border_mode,CvScalar _border_value)157 void CvMorphology::init( int _operation, int _max_width, int _src_dst_type,
158 int _element_shape, CvMat* _element,
159 CvSize _ksize, CvPoint _anchor,
160 int _border_mode, CvScalar _border_value )
161 {
162 CV_FUNCNAME( "CvMorphology::init" );
163
164 __BEGIN__;
165
166 int depth = CV_MAT_DEPTH(_src_dst_type);
167 int el_type = 0, nz = -1;
168
169 if( _operation != ERODE && _operation != DILATE )
170 CV_ERROR( CV_StsBadArg, "Unknown/unsupported morphological operation" );
171
172 if( _element_shape == CUSTOM )
173 {
174 if( !CV_IS_MAT(_element) )
175 CV_ERROR( CV_StsBadArg,
176 "structuring element should be valid matrix if CUSTOM element shape is specified" );
177
178 el_type = CV_MAT_TYPE(_element->type);
179 if( el_type != CV_8UC1 && el_type != CV_32SC1 )
180 CV_ERROR( CV_StsUnsupportedFormat, "the structuring element must have 8uC1 or 32sC1 type" );
181
182 _ksize = cvGetMatSize(_element);
183 CV_CALL( nz = cvCountNonZero(_element));
184 if( nz == _ksize.width*_ksize.height )
185 _element_shape = RECT;
186 }
187
188 operation = _operation;
189 el_shape = _element_shape;
190
191 CV_CALL( CvBaseImageFilter::init( _max_width, _src_dst_type, _src_dst_type,
192 _element_shape == RECT, _ksize, _anchor, _border_mode, _border_value ));
193
194 if( el_shape == RECT )
195 {
196 if( operation == ERODE )
197 {
198 if( depth == CV_8U )
199 x_func = (CvRowFilterFunc)icvErodeRectRow_8u,
200 y_func = (CvColumnFilterFunc)icvErodeRectCol_8u;
201 else if( depth == CV_16U )
202 x_func = (CvRowFilterFunc)icvErodeRectRow_16u,
203 y_func = (CvColumnFilterFunc)icvErodeRectCol_16u;
204 else if( depth == CV_32F )
205 x_func = (CvRowFilterFunc)icvErodeRectRow_32f,
206 y_func = (CvColumnFilterFunc)icvErodeRectCol_32f;
207 }
208 else
209 {
210 assert( operation == DILATE );
211 if( depth == CV_8U )
212 x_func = (CvRowFilterFunc)icvDilateRectRow_8u,
213 y_func = (CvColumnFilterFunc)icvDilateRectCol_8u;
214 else if( depth == CV_16U )
215 x_func = (CvRowFilterFunc)icvDilateRectRow_16u,
216 y_func = (CvColumnFilterFunc)icvDilateRectCol_16u;
217 else if( depth == CV_32F )
218 x_func = (CvRowFilterFunc)icvDilateRectRow_32f,
219 y_func = (CvColumnFilterFunc)icvDilateRectCol_32f;
220 }
221 }
222 else
223 {
224 int i, j, k = 0;
225 int cn = CV_MAT_CN(src_type);
226 CvPoint* nz_loc;
227
228 if( !(element && el_sparse &&
229 _ksize.width == element->cols && _ksize.height == element->rows) )
230 {
231 cvReleaseMat( &element );
232 cvFree( &el_sparse );
233 CV_CALL( element = cvCreateMat( _ksize.height, _ksize.width, CV_8UC1 ));
234 CV_CALL( el_sparse = (uchar*)cvAlloc(
235 ksize.width*ksize.height*(2*sizeof(int) + sizeof(uchar*))));
236 }
237
238 if( el_shape == CUSTOM )
239 {
240 CV_CALL( cvConvert( _element, element ));
241 }
242 else
243 {
244 CV_CALL( init_binary_element( element, el_shape, anchor ));
245 }
246
247 if( operation == ERODE )
248 {
249 if( depth == CV_8U )
250 y_func = (CvColumnFilterFunc)icvErodeAny_8u;
251 else if( depth == CV_16U )
252 y_func = (CvColumnFilterFunc)icvErodeAny_16u;
253 else if( depth == CV_32F )
254 y_func = (CvColumnFilterFunc)icvErodeAny_32f;
255 }
256 else
257 {
258 assert( operation == DILATE );
259 if( depth == CV_8U )
260 y_func = (CvColumnFilterFunc)icvDilateAny_8u;
261 else if( depth == CV_16U )
262 y_func = (CvColumnFilterFunc)icvDilateAny_16u;
263 else if( depth == CV_32F )
264 y_func = (CvColumnFilterFunc)icvDilateAny_32f;
265 }
266
267 nz_loc = (CvPoint*)el_sparse;
268
269 for( i = 0; i < ksize.height; i++ )
270 for( j = 0; j < ksize.width; j++ )
271 {
272 if( element->data.ptr[i*element->step+j] )
273 nz_loc[k++] = cvPoint(j*cn,i);
274 }
275 if( k == 0 )
276 nz_loc[k++] = cvPoint(anchor.x*cn,anchor.y);
277 el_sparse_count = k;
278 }
279
280 if( depth == CV_32F && border_mode == IPL_BORDER_CONSTANT )
281 {
282 int i, cn = CV_MAT_CN(src_type);
283 int* bt = (int*)border_tab;
284 for( i = 0; i < cn; i++ )
285 bt[i] = CV_TOGGLE_FLT(bt[i]);
286 }
287
288 __END__;
289 }
290
291
init(int _max_width,int _src_type,int _dst_type,bool _is_separable,CvSize _ksize,CvPoint _anchor,int _border_mode,CvScalar _border_value)292 void CvMorphology::init( int _max_width, int _src_type, int _dst_type,
293 bool _is_separable, CvSize _ksize,
294 CvPoint _anchor, int _border_mode,
295 CvScalar _border_value )
296 {
297 CvBaseImageFilter::init( _max_width, _src_type, _dst_type, _is_separable,
298 _ksize, _anchor, _border_mode, _border_value );
299 }
300
301
start_process(CvSlice x_range,int width)302 void CvMorphology::start_process( CvSlice x_range, int width )
303 {
304 CvBaseImageFilter::start_process( x_range, width );
305 if( el_shape == RECT )
306 {
307 // cut the cyclic buffer off by 1 line if need, to make
308 // the vertical part of separable morphological filter
309 // always process 2 rows at once (except, may be,
310 // for the last one in a stripe).
311 int t = buf_max_count - max_ky*2;
312 if( t > 1 && t % 2 != 0 )
313 {
314 buf_max_count--;
315 buf_end -= buf_step;
316 }
317 }
318 }
319
320
fill_cyclic_buffer(const uchar * src,int src_step,int y0,int y1,int y2)321 int CvMorphology::fill_cyclic_buffer( const uchar* src, int src_step,
322 int y0, int y1, int y2 )
323 {
324 int i, y = y0, bsz1 = border_tab_sz1, bsz = border_tab_sz;
325 int pix_size = CV_ELEM_SIZE(src_type);
326 int width_n = (prev_x_range.end_index - prev_x_range.start_index)*pix_size;
327
328 if( CV_MAT_DEPTH(src_type) != CV_32F )
329 return CvBaseImageFilter::fill_cyclic_buffer( src, src_step, y0, y1, y2 );
330
331 // fill the cyclic buffer
332 for( ; buf_count < buf_max_count && y < y2; buf_count++, y++, src += src_step )
333 {
334 uchar* trow = is_separable ? buf_end : buf_tail;
335
336 for( i = 0; i < width_n; i += sizeof(int) )
337 {
338 int t = *(int*)(src + i);
339 *(int*)(trow + i + bsz1) = CV_TOGGLE_FLT(t);
340 }
341
342 if( border_mode != IPL_BORDER_CONSTANT )
343 {
344 for( i = 0; i < bsz1; i++ )
345 {
346 int j = border_tab[i];
347 trow[i] = trow[j];
348 }
349 for( ; i < bsz; i++ )
350 {
351 int j = border_tab[i];
352 trow[i + width_n] = trow[j];
353 }
354 }
355 else
356 {
357 const uchar *bt = (uchar*)border_tab;
358 for( i = 0; i < bsz1; i++ )
359 trow[i] = bt[i];
360
361 for( ; i < bsz; i++ )
362 trow[i + width_n] = bt[i];
363 }
364
365 if( is_separable )
366 x_func( trow, buf_tail, this );
367
368 buf_tail += buf_step;
369 if( buf_tail >= buf_end )
370 buf_tail = buf_start;
371 }
372
373 return y - y0;
374 }
375
376
init_binary_element(CvMat * element,int element_shape,CvPoint anchor)377 void CvMorphology::init_binary_element( CvMat* element, int element_shape, CvPoint anchor )
378 {
379 CV_FUNCNAME( "CvMorphology::init_binary_element" );
380
381 __BEGIN__;
382
383 int type;
384 int i, j, cols, rows;
385 int r = 0, c = 0;
386 double inv_r2 = 0;
387
388 if( !CV_IS_MAT(element) )
389 CV_ERROR( CV_StsBadArg, "element must be valid matrix" );
390
391 type = CV_MAT_TYPE(element->type);
392 if( type != CV_8UC1 && type != CV_32SC1 )
393 CV_ERROR( CV_StsUnsupportedFormat, "element must have 8uC1 or 32sC1 type" );
394
395 if( anchor.x == -1 )
396 anchor.x = element->cols/2;
397
398 if( anchor.y == -1 )
399 anchor.y = element->rows/2;
400
401 if( (unsigned)anchor.x >= (unsigned)element->cols ||
402 (unsigned)anchor.y >= (unsigned)element->rows )
403 CV_ERROR( CV_StsOutOfRange, "anchor is outside of element" );
404
405 if( element_shape != RECT && element_shape != CROSS && element_shape != ELLIPSE )
406 CV_ERROR( CV_StsBadArg, "Unknown/unsupported element shape" );
407
408 rows = element->rows;
409 cols = element->cols;
410
411 if( rows == 1 || cols == 1 )
412 element_shape = RECT;
413
414 if( element_shape == ELLIPSE )
415 {
416 r = rows/2;
417 c = cols/2;
418 inv_r2 = r ? 1./((double)r*r) : 0;
419 }
420
421 for( i = 0; i < rows; i++ )
422 {
423 uchar* ptr = element->data.ptr + i*element->step;
424 int j1 = 0, j2 = 0, jx, t = 0;
425
426 if( element_shape == RECT || (element_shape == CROSS && i == anchor.y) )
427 j2 = cols;
428 else if( element_shape == CROSS )
429 j1 = anchor.x, j2 = j1 + 1;
430 else
431 {
432 int dy = i - r;
433 if( abs(dy) <= r )
434 {
435 int dx = cvRound(c*sqrt(((double)r*r - dy*dy)*inv_r2));
436 j1 = MAX( c - dx, 0 );
437 j2 = MIN( c + dx + 1, cols );
438 }
439 }
440
441 for( j = 0, jx = j1; j < cols; )
442 {
443 for( ; j < jx; j++ )
444 {
445 if( type == CV_8UC1 )
446 ptr[j] = (uchar)t;
447 else
448 ((int*)ptr)[j] = t;
449 }
450 if( jx == j2 )
451 jx = cols, t = 0;
452 else
453 jx = j2, t = 1;
454 }
455 }
456
457 __END__;
458 }
459
460
461 #define ICV_MORPH_RECT_ROW( name, flavor, arrtype, \
462 worktype, update_extr_macro ) \
463 static void \
464 icv##name##RectRow_##flavor( const arrtype* src, \
465 arrtype* dst, void* params ) \
466 { \
467 const CvMorphology* state = (const CvMorphology*)params;\
468 int ksize = state->get_kernel_size().width; \
469 int width = state->get_width(); \
470 int cn = CV_MAT_CN(state->get_src_type()); \
471 int i, j, k; \
472 \
473 width *= cn; ksize *= cn; \
474 \
475 if( ksize == cn ) \
476 { \
477 for( i = 0; i < width; i++ ) \
478 dst[i] = src[i]; \
479 return; \
480 } \
481 \
482 for( k = 0; k < cn; k++, src++, dst++ ) \
483 { \
484 for( i = 0; i <= width - cn*2; i += cn*2 ) \
485 { \
486 const arrtype* s = src + i; \
487 worktype m = s[cn], t; \
488 for( j = cn*2; j < ksize; j += cn ) \
489 { \
490 t = s[j]; update_extr_macro(m,t); \
491 } \
492 t = s[0]; update_extr_macro(t,m); \
493 dst[i] = (arrtype)t; \
494 t = s[j]; update_extr_macro(t,m); \
495 dst[i+cn] = (arrtype)t; \
496 } \
497 \
498 for( ; i < width; i += cn ) \
499 { \
500 const arrtype* s = src + i; \
501 worktype m = s[0], t; \
502 for( j = cn; j < ksize; j += cn ) \
503 { \
504 t = s[j]; update_extr_macro(m,t); \
505 } \
506 dst[i] = (arrtype)m; \
507 } \
508 } \
509 }
510
511
512 ICV_MORPH_RECT_ROW( Erode, 8u, uchar, int, CV_CALC_MIN_8U )
513 ICV_MORPH_RECT_ROW( Dilate, 8u, uchar, int, CV_CALC_MAX_8U )
514 ICV_MORPH_RECT_ROW( Erode, 16u, ushort, int, CV_CALC_MIN )
515 ICV_MORPH_RECT_ROW( Dilate, 16u, ushort, int, CV_CALC_MAX )
516 ICV_MORPH_RECT_ROW( Erode, 32f, int, int, CV_CALC_MIN )
517 ICV_MORPH_RECT_ROW( Dilate, 32f, int, int, CV_CALC_MAX )
518
519
520 #define ICV_MORPH_RECT_COL( name, flavor, arrtype, \
521 worktype, update_extr_macro, toggle_macro ) \
522 static void \
523 icv##name##RectCol_##flavor( const arrtype** src, \
524 arrtype* dst, int dst_step, int count, void* params ) \
525 { \
526 const CvMorphology* state = (const CvMorphology*)params;\
527 int ksize = state->get_kernel_size().height; \
528 int width = state->get_width(); \
529 int cn = CV_MAT_CN(state->get_src_type()); \
530 int i, k; \
531 \
532 width *= cn; \
533 dst_step /= sizeof(dst[0]); \
534 \
535 for( ; ksize > 1 && count > 1; count -= 2, \
536 dst += dst_step*2, src += 2 ) \
537 { \
538 for( i = 0; i <= width - 4; i += 4 ) \
539 { \
540 const arrtype* sptr = src[1] + i; \
541 worktype s0 = sptr[0], s1 = sptr[1], \
542 s2 = sptr[2], s3 = sptr[3], t0, t1; \
543 \
544 for( k = 2; k < ksize; k++ ) \
545 { \
546 sptr = src[k] + i; \
547 t0 = sptr[0]; t1 = sptr[1]; \
548 update_extr_macro(s0,t0); \
549 update_extr_macro(s1,t1); \
550 t0 = sptr[2]; t1 = sptr[3]; \
551 update_extr_macro(s2,t0); \
552 update_extr_macro(s3,t1); \
553 } \
554 \
555 sptr = src[0] + i; \
556 t0 = sptr[0]; t1 = sptr[1]; \
557 update_extr_macro(t0,s0); \
558 update_extr_macro(t1,s1); \
559 dst[i] = (arrtype)toggle_macro(t0); \
560 dst[i+1] = (arrtype)toggle_macro(t1); \
561 t0 = sptr[2]; t1 = sptr[3]; \
562 update_extr_macro(t0,s2); \
563 update_extr_macro(t1,s3); \
564 dst[i+2] = (arrtype)toggle_macro(t0); \
565 dst[i+3] = (arrtype)toggle_macro(t1); \
566 \
567 sptr = src[k] + i; \
568 t0 = sptr[0]; t1 = sptr[1]; \
569 update_extr_macro(t0,s0); \
570 update_extr_macro(t1,s1); \
571 dst[i+dst_step] = (arrtype)toggle_macro(t0); \
572 dst[i+dst_step+1] = (arrtype)toggle_macro(t1); \
573 t0 = sptr[2]; t1 = sptr[3]; \
574 update_extr_macro(t0,s2); \
575 update_extr_macro(t1,s3); \
576 dst[i+dst_step+2] = (arrtype)toggle_macro(t0); \
577 dst[i+dst_step+3] = (arrtype)toggle_macro(t1); \
578 } \
579 \
580 for( ; i < width; i++ ) \
581 { \
582 const arrtype* sptr = src[1] + i; \
583 worktype s0 = sptr[0], t0; \
584 \
585 for( k = 2; k < ksize; k++ ) \
586 { \
587 sptr = src[k] + i; t0 = sptr[0]; \
588 update_extr_macro(s0,t0); \
589 } \
590 \
591 sptr = src[0] + i; t0 = sptr[0]; \
592 update_extr_macro(t0,s0); \
593 dst[i] = (arrtype)toggle_macro(t0); \
594 \
595 sptr = src[k] + i; t0 = sptr[0]; \
596 update_extr_macro(t0,s0); \
597 dst[i+dst_step] = (arrtype)toggle_macro(t0); \
598 } \
599 } \
600 \
601 for( ; count > 0; count--, dst += dst_step, src++ ) \
602 { \
603 for( i = 0; i <= width - 4; i += 4 ) \
604 { \
605 const arrtype* sptr = src[0] + i; \
606 worktype s0 = sptr[0], s1 = sptr[1], \
607 s2 = sptr[2], s3 = sptr[3], t0, t1; \
608 \
609 for( k = 1; k < ksize; k++ ) \
610 { \
611 sptr = src[k] + i; \
612 t0 = sptr[0]; t1 = sptr[1]; \
613 update_extr_macro(s0,t0); \
614 update_extr_macro(s1,t1); \
615 t0 = sptr[2]; t1 = sptr[3]; \
616 update_extr_macro(s2,t0); \
617 update_extr_macro(s3,t1); \
618 } \
619 dst[i] = (arrtype)toggle_macro(s0); \
620 dst[i+1] = (arrtype)toggle_macro(s1); \
621 dst[i+2] = (arrtype)toggle_macro(s2); \
622 dst[i+3] = (arrtype)toggle_macro(s3); \
623 } \
624 \
625 for( ; i < width; i++ ) \
626 { \
627 const arrtype* sptr = src[0] + i; \
628 worktype s0 = sptr[0], t0; \
629 \
630 for( k = 1; k < ksize; k++ ) \
631 { \
632 sptr = src[k] + i; t0 = sptr[0]; \
633 update_extr_macro(s0,t0); \
634 } \
635 dst[i] = (arrtype)toggle_macro(s0); \
636 } \
637 } \
638 }
639
640
641 ICV_MORPH_RECT_COL( Erode, 8u, uchar, int, CV_CALC_MIN_8U, CV_NOP )
642 ICV_MORPH_RECT_COL( Dilate, 8u, uchar, int, CV_CALC_MAX_8U, CV_NOP )
643 ICV_MORPH_RECT_COL( Erode, 16u, ushort, int, CV_CALC_MIN, CV_NOP )
644 ICV_MORPH_RECT_COL( Dilate, 16u, ushort, int, CV_CALC_MAX, CV_NOP )
645 ICV_MORPH_RECT_COL( Erode, 32f, int, int, CV_CALC_MIN, CV_TOGGLE_FLT )
646 ICV_MORPH_RECT_COL( Dilate, 32f, int, int, CV_CALC_MAX, CV_TOGGLE_FLT )
647
648
649 #define ICV_MORPH_ANY( name, flavor, arrtype, worktype, \
650 update_extr_macro, toggle_macro ) \
651 static void \
652 icv##name##Any_##flavor( const arrtype** src, arrtype* dst, \
653 int dst_step, int count, void* params ) \
654 { \
655 CvMorphology* state = (CvMorphology*)params; \
656 int width = state->get_width(); \
657 int cn = CV_MAT_CN(state->get_src_type()); \
658 int i, k; \
659 CvPoint* el_sparse = (CvPoint*)state->get_element_sparse_buf();\
660 int el_count = state->get_element_sparse_count(); \
661 const arrtype** el_ptr = (const arrtype**)(el_sparse + el_count);\
662 const arrtype** el_end = el_ptr + el_count; \
663 \
664 width *= cn; \
665 dst_step /= sizeof(dst[0]); \
666 \
667 for( ; count > 0; count--, dst += dst_step, src++ ) \
668 { \
669 for( k = 0; k < el_count; k++ ) \
670 el_ptr[k] = src[el_sparse[k].y]+el_sparse[k].x; \
671 \
672 for( i = 0; i <= width - 4; i += 4 ) \
673 { \
674 const arrtype** psptr = el_ptr; \
675 const arrtype* sptr = *psptr++; \
676 worktype s0 = sptr[i], s1 = sptr[i+1], \
677 s2 = sptr[i+2], s3 = sptr[i+3], t; \
678 \
679 while( psptr != el_end ) \
680 { \
681 sptr = *psptr++; \
682 t = sptr[i]; \
683 update_extr_macro(s0,t); \
684 t = sptr[i+1]; \
685 update_extr_macro(s1,t); \
686 t = sptr[i+2]; \
687 update_extr_macro(s2,t); \
688 t = sptr[i+3]; \
689 update_extr_macro(s3,t); \
690 } \
691 \
692 dst[i] = (arrtype)toggle_macro(s0); \
693 dst[i+1] = (arrtype)toggle_macro(s1); \
694 dst[i+2] = (arrtype)toggle_macro(s2); \
695 dst[i+3] = (arrtype)toggle_macro(s3); \
696 } \
697 \
698 for( ; i < width; i++ ) \
699 { \
700 const arrtype* sptr = el_ptr[0] + i; \
701 worktype s0 = sptr[0], t0; \
702 \
703 for( k = 1; k < el_count; k++ ) \
704 { \
705 sptr = el_ptr[k] + i; \
706 t0 = sptr[0]; \
707 update_extr_macro(s0,t0); \
708 } \
709 \
710 dst[i] = (arrtype)toggle_macro(s0); \
711 } \
712 } \
713 }
714
715 ICV_MORPH_ANY( Erode, 8u, uchar, int, CV_CALC_MIN, CV_NOP )
716 ICV_MORPH_ANY( Dilate, 8u, uchar, int, CV_CALC_MAX, CV_NOP )
717 ICV_MORPH_ANY( Erode, 16u, ushort, int, CV_CALC_MIN, CV_NOP )
718 ICV_MORPH_ANY( Dilate, 16u, ushort, int, CV_CALC_MAX, CV_NOP )
719 ICV_MORPH_ANY( Erode, 32f, int, int, CV_CALC_MIN, CV_TOGGLE_FLT )
720 ICV_MORPH_ANY( Dilate, 32f, int, int, CV_CALC_MAX, CV_TOGGLE_FLT )
721
722 /////////////////////////////////// External Interface /////////////////////////////////////
723
724
725 CV_IMPL IplConvKernel *
cvCreateStructuringElementEx(int cols,int rows,int anchorX,int anchorY,int shape,int * values)726 cvCreateStructuringElementEx( int cols, int rows,
727 int anchorX, int anchorY,
728 int shape, int *values )
729 {
730 IplConvKernel *element = 0;
731 int i, size = rows * cols;
732 int element_size = sizeof(*element) + size*sizeof(element->values[0]);
733
734 CV_FUNCNAME( "cvCreateStructuringElementEx" );
735
736 __BEGIN__;
737
738 if( !values && shape == CV_SHAPE_CUSTOM )
739 CV_ERROR_FROM_STATUS( CV_NULLPTR_ERR );
740
741 if( cols <= 0 || rows <= 0 ||
742 (unsigned) anchorX >= (unsigned) cols ||
743 (unsigned) anchorY >= (unsigned) rows )
744 CV_ERROR_FROM_STATUS( CV_BADSIZE_ERR );
745
746 CV_CALL( element = (IplConvKernel *)cvAlloc(element_size + 32));
747 if( !element )
748 CV_ERROR_FROM_STATUS( CV_OUTOFMEM_ERR );
749
750 element->nCols = cols;
751 element->nRows = rows;
752 element->anchorX = anchorX;
753 element->anchorY = anchorY;
754 element->nShiftR = shape < CV_SHAPE_ELLIPSE ? shape : CV_SHAPE_CUSTOM;
755 element->values = (int*)(element + 1);
756
757 if( shape == CV_SHAPE_CUSTOM )
758 {
759 if( !values )
760 CV_ERROR( CV_StsNullPtr, "Null pointer to the custom element mask" );
761 for( i = 0; i < size; i++ )
762 element->values[i] = values[i];
763 }
764 else
765 {
766 CvMat el_hdr = cvMat( rows, cols, CV_32SC1, element->values );
767 CV_CALL( CvMorphology::init_binary_element(&el_hdr,
768 shape, cvPoint(anchorX,anchorY)));
769 }
770
771 __END__;
772
773 if( cvGetErrStatus() < 0 )
774 cvReleaseStructuringElement( &element );
775
776 return element;
777 }
778
779
780 CV_IMPL void
cvReleaseStructuringElement(IplConvKernel ** element)781 cvReleaseStructuringElement( IplConvKernel ** element )
782 {
783 CV_FUNCNAME( "cvReleaseStructuringElement" );
784
785 __BEGIN__;
786
787 if( !element )
788 CV_ERROR( CV_StsNullPtr, "" );
789 cvFree( element );
790
791 __END__;
792 }
793
794
795 typedef CvStatus (CV_STDCALL * CvMorphRectGetBufSizeFunc_IPP)
796 ( int width, CvSize el_size, int* bufsize );
797
798 typedef CvStatus (CV_STDCALL * CvMorphRectFunc_IPP)
799 ( const void* src, int srcstep, void* dst, int dststep,
800 CvSize roi, CvSize el_size, CvPoint el_anchor, void* buffer );
801
802 typedef CvStatus (CV_STDCALL * CvMorphCustomInitAllocFunc_IPP)
803 ( int width, const uchar* element, CvSize el_size,
804 CvPoint el_anchor, void** morphstate );
805
806 typedef CvStatus (CV_STDCALL * CvMorphCustomFunc_IPP)
807 ( const void* src, int srcstep, void* dst, int dststep,
808 CvSize roi, int bordertype, void* morphstate );
809
810 static void
icvMorphOp(const void * srcarr,void * dstarr,IplConvKernel * element,int iterations,int mop)811 icvMorphOp( const void* srcarr, void* dstarr, IplConvKernel* element,
812 int iterations, int mop )
813 {
814 CvMorphology morphology;
815 void* buffer = 0;
816 int local_alloc = 0;
817 void* morphstate = 0;
818 CvMat* temp = 0;
819
820 CV_FUNCNAME( "icvMorphOp" );
821
822 __BEGIN__;
823
824 int i, coi1 = 0, coi2 = 0;
825 CvMat srcstub, *src = (CvMat*)srcarr;
826 CvMat dststub, *dst = (CvMat*)dstarr;
827 CvMat el_hdr, *el = 0;
828 CvSize size, el_size;
829 CvPoint el_anchor;
830 int el_shape;
831 int type;
832 bool inplace;
833
834 if( !CV_IS_MAT(src) )
835 CV_CALL( src = cvGetMat( src, &srcstub, &coi1 ));
836
837 if( src != &srcstub )
838 {
839 srcstub = *src;
840 src = &srcstub;
841 }
842
843 if( dstarr == srcarr )
844 dst = src;
845 else
846 {
847 CV_CALL( dst = cvGetMat( dst, &dststub, &coi2 ));
848
849 if( !CV_ARE_TYPES_EQ( src, dst ))
850 CV_ERROR( CV_StsUnmatchedFormats, "" );
851
852 if( !CV_ARE_SIZES_EQ( src, dst ))
853 CV_ERROR( CV_StsUnmatchedSizes, "" );
854 }
855
856 if( dst != &dststub )
857 {
858 dststub = *dst;
859 dst = &dststub;
860 }
861
862 if( coi1 != 0 || coi2 != 0 )
863 CV_ERROR( CV_BadCOI, "" );
864
865 type = CV_MAT_TYPE( src->type );
866 size = cvGetMatSize( src );
867 inplace = src->data.ptr == dst->data.ptr;
868
869 if( iterations == 0 || (element && element->nCols == 1 && element->nRows == 1))
870 {
871 if( src->data.ptr != dst->data.ptr )
872 cvCopy( src, dst );
873 EXIT;
874 }
875
876 if( element )
877 {
878 el_size = cvSize( element->nCols, element->nRows );
879 el_anchor = cvPoint( element->anchorX, element->anchorY );
880 el_shape = (int)(element->nShiftR);
881 el_shape = el_shape < CV_SHAPE_CUSTOM ? el_shape : CV_SHAPE_CUSTOM;
882 }
883 else
884 {
885 el_size = cvSize(3,3);
886 el_anchor = cvPoint(1,1);
887 el_shape = CV_SHAPE_RECT;
888 }
889
890 if( el_shape == CV_SHAPE_RECT && iterations > 1 )
891 {
892 el_size.width = 1 + (el_size.width-1)*iterations;
893 el_size.height = 1 + (el_size.height-1)*iterations;
894 el_anchor.x *= iterations;
895 el_anchor.y *= iterations;
896 iterations = 1;
897 }
898
899 if( el_shape == CV_SHAPE_RECT && icvErodeRect_GetBufSize_8u_C1R_p )
900 {
901 CvMorphRectFunc_IPP rect_func = 0;
902 CvMorphRectGetBufSizeFunc_IPP rect_getbufsize_func = 0;
903
904 if( mop == 0 )
905 {
906 if( type == CV_8UC1 )
907 rect_getbufsize_func = icvErodeRect_GetBufSize_8u_C1R_p,
908 rect_func = icvErodeRect_8u_C1R_p;
909 else if( type == CV_8UC3 )
910 rect_getbufsize_func = icvErodeRect_GetBufSize_8u_C3R_p,
911 rect_func = icvErodeRect_8u_C3R_p;
912 else if( type == CV_8UC4 )
913 rect_getbufsize_func = icvErodeRect_GetBufSize_8u_C4R_p,
914 rect_func = icvErodeRect_8u_C4R_p;
915 else if( type == CV_16UC1 )
916 rect_getbufsize_func = icvErodeRect_GetBufSize_16u_C1R_p,
917 rect_func = icvErodeRect_16u_C1R_p;
918 else if( type == CV_16UC3 )
919 rect_getbufsize_func = icvErodeRect_GetBufSize_16u_C3R_p,
920 rect_func = icvErodeRect_16u_C3R_p;
921 else if( type == CV_16UC4 )
922 rect_getbufsize_func = icvErodeRect_GetBufSize_16u_C4R_p,
923 rect_func = icvErodeRect_16u_C4R_p;
924 else if( type == CV_32FC1 )
925 rect_getbufsize_func = icvErodeRect_GetBufSize_32f_C1R_p,
926 rect_func = icvErodeRect_32f_C1R_p;
927 else if( type == CV_32FC3 )
928 rect_getbufsize_func = icvErodeRect_GetBufSize_32f_C3R_p,
929 rect_func = icvErodeRect_32f_C3R_p;
930 else if( type == CV_32FC4 )
931 rect_getbufsize_func = icvErodeRect_GetBufSize_32f_C4R_p,
932 rect_func = icvErodeRect_32f_C4R_p;
933 }
934 else
935 {
936 if( type == CV_8UC1 )
937 rect_getbufsize_func = icvDilateRect_GetBufSize_8u_C1R_p,
938 rect_func = icvDilateRect_8u_C1R_p;
939 else if( type == CV_8UC3 )
940 rect_getbufsize_func = icvDilateRect_GetBufSize_8u_C3R_p,
941 rect_func = icvDilateRect_8u_C3R_p;
942 else if( type == CV_8UC4 )
943 rect_getbufsize_func = icvDilateRect_GetBufSize_8u_C4R_p,
944 rect_func = icvDilateRect_8u_C4R_p;
945 else if( type == CV_16UC1 )
946 rect_getbufsize_func = icvDilateRect_GetBufSize_16u_C1R_p,
947 rect_func = icvDilateRect_16u_C1R_p;
948 else if( type == CV_16UC3 )
949 rect_getbufsize_func = icvDilateRect_GetBufSize_16u_C3R_p,
950 rect_func = icvDilateRect_16u_C3R_p;
951 else if( type == CV_16UC4 )
952 rect_getbufsize_func = icvDilateRect_GetBufSize_16u_C4R_p,
953 rect_func = icvDilateRect_16u_C4R_p;
954 else if( type == CV_32FC1 )
955 rect_getbufsize_func = icvDilateRect_GetBufSize_32f_C1R_p,
956 rect_func = icvDilateRect_32f_C1R_p;
957 else if( type == CV_32FC3 )
958 rect_getbufsize_func = icvDilateRect_GetBufSize_32f_C3R_p,
959 rect_func = icvDilateRect_32f_C3R_p;
960 else if( type == CV_32FC4 )
961 rect_getbufsize_func = icvDilateRect_GetBufSize_32f_C4R_p,
962 rect_func = icvDilateRect_32f_C4R_p;
963 }
964
965 if( rect_getbufsize_func && rect_func )
966 {
967 int bufsize = 0;
968
969 CvStatus status = rect_getbufsize_func( size.width, el_size, &bufsize );
970 if( status >= 0 && bufsize > 0 )
971 {
972 if( bufsize < CV_MAX_LOCAL_SIZE )
973 {
974 buffer = cvStackAlloc( bufsize );
975 local_alloc = 1;
976 }
977 else
978 CV_CALL( buffer = cvAlloc( bufsize ));
979 }
980
981 if( status >= 0 )
982 {
983 int src_step, dst_step = dst->step ? dst->step : CV_STUB_STEP;
984
985 if( inplace )
986 {
987 CV_CALL( temp = cvCloneMat( dst ));
988 src = temp;
989 }
990 src_step = src->step ? src->step : CV_STUB_STEP;
991
992 status = rect_func( src->data.ptr, src_step, dst->data.ptr,
993 dst_step, size, el_size, el_anchor, buffer );
994 }
995
996 if( status >= 0 )
997 EXIT;
998 }
999 }
1000 else if( el_shape == CV_SHAPE_CUSTOM && icvMorphInitAlloc_8u_C1R_p && icvMorphFree_p &&
1001 src->data.ptr != dst->data.ptr )
1002 {
1003 CvMorphCustomFunc_IPP custom_func = 0;
1004 CvMorphCustomInitAllocFunc_IPP custom_initalloc_func = 0;
1005 const int bordertype = 1; // replication border
1006
1007 if( type == CV_8UC1 )
1008 custom_initalloc_func = icvMorphInitAlloc_8u_C1R_p,
1009 custom_func = mop == 0 ? icvErode_8u_C1R_p : icvDilate_8u_C1R_p;
1010 else if( type == CV_8UC3 )
1011 custom_initalloc_func = icvMorphInitAlloc_8u_C3R_p,
1012 custom_func = mop == 0 ? icvErode_8u_C3R_p : icvDilate_8u_C3R_p;
1013 else if( type == CV_8UC4 )
1014 custom_initalloc_func = icvMorphInitAlloc_8u_C4R_p,
1015 custom_func = mop == 0 ? icvErode_8u_C4R_p : icvDilate_8u_C4R_p;
1016 else if( type == CV_16UC1 )
1017 custom_initalloc_func = icvMorphInitAlloc_16u_C1R_p,
1018 custom_func = mop == 0 ? icvErode_16u_C1R_p : icvDilate_16u_C1R_p;
1019 else if( type == CV_16UC3 )
1020 custom_initalloc_func = icvMorphInitAlloc_16u_C3R_p,
1021 custom_func = mop == 0 ? icvErode_16u_C3R_p : icvDilate_16u_C3R_p;
1022 else if( type == CV_16UC4 )
1023 custom_initalloc_func = icvMorphInitAlloc_16u_C4R_p,
1024 custom_func = mop == 0 ? icvErode_16u_C4R_p : icvDilate_16u_C4R_p;
1025 else if( type == CV_32FC1 )
1026 custom_initalloc_func = icvMorphInitAlloc_32f_C1R_p,
1027 custom_func = mop == 0 ? icvErode_32f_C1R_p : icvDilate_32f_C1R_p;
1028 else if( type == CV_32FC3 )
1029 custom_initalloc_func = icvMorphInitAlloc_32f_C3R_p,
1030 custom_func = mop == 0 ? icvErode_32f_C3R_p : icvDilate_32f_C3R_p;
1031 else if( type == CV_32FC4 )
1032 custom_initalloc_func = icvMorphInitAlloc_32f_C4R_p,
1033 custom_func = mop == 0 ? icvErode_32f_C4R_p : icvDilate_32f_C4R_p;
1034
1035 if( custom_initalloc_func && custom_func )
1036 {
1037 uchar *src_ptr, *dst_ptr = dst->data.ptr;
1038 int src_step, dst_step = dst->step ? dst->step : CV_STUB_STEP;
1039 int el_len = el_size.width*el_size.height;
1040 uchar* el_mask = (uchar*)cvStackAlloc( el_len );
1041 CvStatus status;
1042
1043 for( i = 0; i < el_len; i++ )
1044 el_mask[i] = (uchar)(element->values[i] != 0);
1045
1046 status = custom_initalloc_func( size.width, el_mask, el_size,
1047 el_anchor, &morphstate );
1048
1049 if( status >= 0 && (inplace || iterations > 1) )
1050 {
1051 CV_CALL( temp = cvCloneMat( src ));
1052 src = temp;
1053 }
1054
1055 src_ptr = src->data.ptr;
1056 src_step = src->step ? src->step : CV_STUB_STEP;
1057
1058 for( i = 0; i < iterations && status >= 0 && morphstate; i++ )
1059 {
1060 uchar* t_ptr;
1061 int t_step;
1062 status = custom_func( src_ptr, src_step, dst_ptr, dst_step,
1063 size, bordertype, morphstate );
1064 CV_SWAP( src_ptr, dst_ptr, t_ptr );
1065 CV_SWAP( src_step, dst_step, t_step );
1066 if( i == 0 && temp )
1067 {
1068 dst_ptr = temp->data.ptr;
1069 dst_step = temp->step ? temp->step : CV_STUB_STEP;
1070 }
1071 }
1072
1073 if( status >= 0 )
1074 {
1075 if( iterations % 2 == 0 )
1076 cvCopy( temp, dst );
1077 EXIT;
1078 }
1079 }
1080 }
1081
1082 if( el_shape != CV_SHAPE_RECT )
1083 {
1084 el_hdr = cvMat( element->nRows, element->nCols, CV_32SC1, element->values );
1085 el = &el_hdr;
1086 el_shape = CV_SHAPE_CUSTOM;
1087 }
1088
1089 CV_CALL( morphology.init( mop, src->cols, src->type,
1090 el_shape, el, el_size, el_anchor ));
1091
1092 for( i = 0; i < iterations; i++ )
1093 {
1094 CV_CALL( morphology.process( src, dst ));
1095 src = dst;
1096 }
1097
1098 __END__;
1099
1100 if( !local_alloc )
1101 cvFree( &buffer );
1102 if( morphstate )
1103 icvMorphFree_p( morphstate );
1104 cvReleaseMat( &temp );
1105 }
1106
1107
1108 CV_IMPL void
cvErode(const void * src,void * dst,IplConvKernel * element,int iterations)1109 cvErode( const void* src, void* dst, IplConvKernel* element, int iterations )
1110 {
1111 icvMorphOp( src, dst, element, iterations, 0 );
1112 }
1113
1114
1115 CV_IMPL void
cvDilate(const void * src,void * dst,IplConvKernel * element,int iterations)1116 cvDilate( const void* src, void* dst, IplConvKernel* element, int iterations )
1117 {
1118 icvMorphOp( src, dst, element, iterations, 1 );
1119 }
1120
1121
1122 CV_IMPL void
cvMorphologyEx(const void * src,void * dst,void * temp,IplConvKernel * element,int op,int iterations)1123 cvMorphologyEx( const void* src, void* dst,
1124 void* temp, IplConvKernel* element, int op, int iterations )
1125 {
1126 CV_FUNCNAME( "cvMorhologyEx" );
1127
1128 __BEGIN__;
1129
1130 if( (op == CV_MOP_GRADIENT ||
1131 ((op == CV_MOP_TOPHAT || op == CV_MOP_BLACKHAT) && src == dst)) && temp == 0 )
1132 CV_ERROR( CV_HeaderIsNull, "temp image required" );
1133
1134 if( temp == src || temp == dst )
1135 CV_ERROR( CV_HeaderIsNull, "temp image is equal to src or dst" );
1136
1137 switch (op)
1138 {
1139 case CV_MOP_OPEN:
1140 CV_CALL( cvErode( src, dst, element, iterations ));
1141 CV_CALL( cvDilate( dst, dst, element, iterations ));
1142 break;
1143 case CV_MOP_CLOSE:
1144 CV_CALL( cvDilate( src, dst, element, iterations ));
1145 CV_CALL( cvErode( dst, dst, element, iterations ));
1146 break;
1147 case CV_MOP_GRADIENT:
1148 CV_CALL( cvErode( src, temp, element, iterations ));
1149 CV_CALL( cvDilate( src, dst, element, iterations ));
1150 CV_CALL( cvSub( dst, temp, dst ));
1151 break;
1152 case CV_MOP_TOPHAT:
1153 if( src != dst )
1154 temp = dst;
1155 CV_CALL( cvErode( src, temp, element, iterations ));
1156 CV_CALL( cvDilate( temp, temp, element, iterations ));
1157 CV_CALL( cvSub( src, temp, dst ));
1158 break;
1159 case CV_MOP_BLACKHAT:
1160 if( src != dst )
1161 temp = dst;
1162 CV_CALL( cvDilate( src, temp, element, iterations ));
1163 CV_CALL( cvErode( temp, temp, element, iterations ));
1164 CV_CALL( cvSub( temp, src, dst ));
1165 break;
1166 default:
1167 CV_ERROR( CV_StsBadArg, "unknown morphological operation" );
1168 }
1169
1170 __END__;
1171 }
1172
1173 /* End of file. */
1174