1 /* pgmmorphconv.c - morphological convolutions on a graymap: dilation and
2 ** erosion
3 **
4 ** Copyright (C) 2000 by Luuk van Dijk/Mind over Matter
5 **
6 ** Based on
7 ** pnmconvol.c - general MxN convolution on a portable anymap
8 **
9 ** Copyright (C) 1989, 1991 by Jef Poskanzer.
10 **
11 ** Permission to use, copy, modify, and distribute this software and its
12 ** documentation for any purpose and without fee is hereby granted, provided
13 ** that the above copyright notice appear in all copies and that both that
14 ** copyright notice and this permission notice appear in supporting
15 ** documentation.  This software is provided "as is" without express or
16 ** implied warranty.
17 */
18 
19 #include "pm_c_util.h"
20 #include "shhopt.h"
21 #include "mallocvar.h"
22 #include "pgm.h"
23 
24 
25 
26 enum Operation { ERODE, DILATE, OPEN, CLOSE, GRADIENT };
27 
28 
29 
30 struct CmdlineInfo {
31     /* All the information the user supplied in the command line,
32        in a form easy for the program to use.
33     */
34     const char * inputFileName;  /* File name of input file */
35     const char * templateFileName;  /* File name of template file */
36 
37     enum Operation operation;
38 };
39 
40 
41 
42 static void
parseCommandLine(int argc,const char ** const argv,struct CmdlineInfo * const cmdlineP)43 parseCommandLine(int argc, const char ** const argv,
44                  struct CmdlineInfo * const cmdlineP) {
45 /*----------------------------------------------------------------------------
46    Note that the file spec array we return is stored in the storage that
47    was passed to us as the argv array.
48 -----------------------------------------------------------------------------*/
49     optEntry * option_def;
50         /* Instructions to OptParseOptions3 on how to parse our options.
51          */
52     optStruct3 opt;
53     unsigned int option_def_index;
54     unsigned int erode, dilate, open, close, gradient;
55 
56     MALLOCARRAY_NOFAIL(option_def, 100);
57 
58     option_def_index = 0;   /* incremented by OPTENT3 */
59     OPTENT3(0,   "erode",        OPT_FLAG,   NULL, &erode,           0);
60     OPTENT3(0,   "dilate",       OPT_FLAG,   NULL, &dilate,          0);
61     OPTENT3(0,   "open",         OPT_FLAG,   NULL, &open,            0);
62     OPTENT3(0,   "close",        OPT_FLAG,   NULL, &close,           0);
63     OPTENT3(0,   "gradient",     OPT_FLAG,   NULL, &gradient,        0);
64 
65     opt.opt_table = option_def;
66     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
67     opt.allowNegNum = TRUE;  /* We may have parms that are negative numbers */
68 
69     pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
70         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
71 
72     if (erode + dilate + open + close + gradient > 1)
73         pm_error("You may specify at most one of -erode, -dilate, "
74                  "-open, -close, or -gradient");
75 
76     if (erode)
77         cmdlineP->operation = ERODE;
78     else if (dilate)
79         cmdlineP->operation = DILATE;
80     else if (open)
81         cmdlineP->operation = OPEN;
82     else if (close)
83         cmdlineP->operation = CLOSE;
84     else if (gradient)
85         cmdlineP->operation = GRADIENT;
86     else
87         cmdlineP->operation = DILATE;
88 
89     if (argc-1 < 1)
90         pm_error("You must specify the template file name as an argument");
91     else {
92         cmdlineP->templateFileName = argv[1];
93 
94         if (argc-1 < 2)
95             cmdlineP->inputFileName = "-";
96         else {
97             cmdlineP->inputFileName = argv[2];
98 
99             if (argc-1 > 2)
100                 pm_error("Too many arguments: %u.  "
101                          "The only possible arguments "
102                          "are the template file name and the input file name",
103                          argc-1);
104         }
105     }
106 }
107 
108 
109 
110 static void
readTemplateMatrix(const char * const fileName,bit *** const templateP,unsigned int * const rowsP,unsigned int * const colsP)111 readTemplateMatrix(const char *   const fileName,
112                    bit ***        const templateP,
113                    unsigned int * const rowsP,
114                    unsigned int * const colsP) {
115 /*----------------------------------------------------------------------------
116   Read in the template matrix.
117 -----------------------------------------------------------------------------*/
118     FILE * templateFileP;
119     int cols, rows;
120 
121     templateFileP = pm_openr(fileName);
122 
123     *templateP = pbm_readpbm(templateFileP, &cols, &rows);
124 
125     pm_close(templateFileP);
126 
127     if (cols % 2 != 1 || rows % 2 != 1)
128         pm_error("the template matrix must have an odd number of "
129                  "rows and columns" );
130 
131         /* the reason is that we want the middle pixel to be the origin */
132     *rowsP = rows;
133     *colsP = cols;
134 }
135 
136 
137 
138 static void
setAllPixel(gray ** const image,unsigned int const rows,unsigned int const cols,gray const value)139 setAllPixel(gray **      const image,
140             unsigned int const rows,
141             unsigned int const cols,
142             gray         const value) {
143 
144     unsigned int col;
145 
146     for (col = 0; col < cols; ++col) {
147         unsigned int row;
148         for (row = 0; row < rows; ++row)
149             image[row][col] = value;
150     }
151 }
152 
153 
154 
155 static void
dilate(bit ** const template,int const trowso2,int const tcolso2,gray ** const inImage,gray ** const outImage,unsigned int const rows,unsigned int const cols,unsigned int * const templateCountP)156 dilate(bit **         const template,
157        int            const trowso2,
158        int            const tcolso2,
159        gray **        const inImage,
160        gray **        const outImage,
161        unsigned int   const rows,
162        unsigned int   const cols,
163        unsigned int * const templateCountP) {
164 
165     unsigned int templateCount;
166     int tr;
167 
168     setAllPixel(outImage, rows, cols, 0);
169 
170     /* for each non-black pixel of the template add in to out */
171 
172     for (tr = -trowso2, templateCount = 0; tr <= trowso2; ++tr) {
173         int tc;
174         for (tc = -tcolso2; tc <= tcolso2; ++tc) {
175             int r;
176             if (template[trowso2+tr][tcolso2+tc] != PBM_BLACK) {
177                 ++templateCount;
178 
179                 for (r = ((tr > 0) ? 0 : -tr);
180                      r < ((tr > 0) ? (rows-tr) : rows);
181                      ++r) {
182                     int c;
183                     for (c = ((tc > 0) ? 0 : -tc);
184                          c < ((tc > 0) ? (cols-tc) : cols);
185                          ++c) {
186                         gray const source = inImage[r+tr][c+tc];
187                         outImage[r][c] = MAX(source, outImage[r][c]);
188                     }
189                 }
190             }
191         }
192     }
193     *templateCountP = templateCount;
194 }
195 
196 
197 
198 static void
erode(bit ** const template,int const trowso2,int const tcolso2,gray ** const inImage,gray ** const outImage,unsigned int const rows,unsigned int const cols,unsigned int * const templateCountP)199 erode(bit **         const template,
200       int            const trowso2,
201       int            const tcolso2,
202       gray **        const inImage,
203       gray **        const outImage,
204       unsigned int   const rows,
205       unsigned int   const cols,
206       unsigned int * const templateCountP) {
207 
208     unsigned int templateCount;
209     int tr;
210 
211     setAllPixel(outImage, rows, cols, PGM_MAXMAXVAL);
212 
213     /* For each non-black pixel of the template add in to out */
214 
215     for (tr = -trowso2, templateCount = 0; tr <= trowso2; ++tr) {
216         int tc;
217         for (tc = -tcolso2; tc <= tcolso2; ++tc) {
218             if (template[trowso2+tr][tcolso2+tc] != PBM_BLACK) {
219                 int r;
220                 ++templateCount;
221 
222                 for (r = ((tr > 0) ? 0 : -tr);
223                      r < ((tr > 0) ? (rows-tr) : rows);
224                      ++r){
225                     int c;
226 
227                     for (c = ((tc > 0) ? 0 : -tc);
228                          c < ((tc > 0) ? (cols-tc) : cols);
229                          ++c) {
230 
231                         gray const source = inImage[r+tr][c+tc];
232                         outImage[r][c] = MIN(source, outImage[r][c]);
233 
234                     }
235                 }
236             }
237         }
238     }
239     *templateCountP = templateCount;
240 }
241 
242 
243 
244 static void
openMorph(bit ** const template,int const trowso2,int const tcolso2,gray ** const inputImage,gray ** const outputImage,unsigned int const rows,unsigned int const cols,unsigned int * const templateCountP)245 openMorph(bit **         const template,
246           int            const trowso2,
247           int            const tcolso2,
248           gray **        const inputImage,
249           gray **        const outputImage,
250           unsigned int   const rows,
251           unsigned int   const cols,
252           unsigned int * const templateCountP) {
253 
254     gray ** erodedImage;
255     unsigned int erodedTemplateCount;
256 
257     erodedImage = pgm_allocarray(cols, rows);
258 
259     erode(template, trowso2, tcolso2,
260           inputImage, erodedImage, rows, cols, &erodedTemplateCount);
261 
262     dilate(template, trowso2, tcolso2,
263            erodedImage, outputImage, rows, cols, templateCountP);
264 
265     pgm_freearray(erodedImage, rows);
266 }
267 
268 
269 
270 static void
closeMorph(bit ** const template,int const trowso2,int const tcolso2,gray ** const inputImage,gray ** const outputImage,unsigned int const rows,unsigned int const cols,unsigned int * const templateCountP)271 closeMorph(bit **         const template,
272            int            const trowso2,
273            int            const tcolso2,
274            gray **        const inputImage,
275            gray **        const outputImage,
276            unsigned int   const rows,
277            unsigned int   const cols,
278            unsigned int * const templateCountP) {
279 
280     gray ** dilatedImage;
281     unsigned int dilatedTemplateCount;
282 
283     dilatedImage = pgm_allocarray(cols, rows);
284 
285     dilate(template, trowso2, tcolso2,
286            inputImage, dilatedImage, rows, cols, &dilatedTemplateCount);
287 
288     erode(template, trowso2, tcolso2,
289           dilatedImage, outputImage, rows, cols, templateCountP);
290 
291     pgm_freearray(dilatedImage, rows);
292 }
293 
294 
295 
296 static void
subtract(gray ** const subtrahendImage,gray ** const subtractorImage,gray ** const outImage,unsigned int const rows,unsigned int const cols)297 subtract(gray **      const subtrahendImage,
298          gray **      const subtractorImage,
299          gray **      const outImage,
300          unsigned int const rows,
301          unsigned int const cols ) {
302 
303     /* (I call the minuend the subtrahend and the subtrahend the subtractor,
304        to be consistent with other arithmetic terminology).
305     */
306 
307     unsigned int c;
308 
309     for (c = 0; c < cols; ++c) {
310         unsigned int r;
311         for (r = 0; r < rows; ++r)
312             outImage[r][c] = subtrahendImage[r][c] - subtractorImage[r][c];
313     }
314 }
315 
316 
317 
318 static void
gradient(bit ** const template,int const trowso2,int const tcolso2,gray ** const inputImage,gray ** const outputImage,unsigned int const rows,unsigned int const cols,unsigned int * const templateCountP)319 gradient(bit **         const template,
320          int            const trowso2,
321          int            const tcolso2,
322          gray **        const inputImage,
323          gray **        const outputImage,
324          unsigned int   const rows,
325          unsigned int   const cols,
326          unsigned int * const templateCountP) {
327 
328     gray ** dilatedImage;
329     gray ** erodedImage;
330     unsigned int dilatedTemplateCount;
331 
332     dilatedImage = pgm_allocarray(cols, rows);
333     erodedImage = pgm_allocarray(cols, rows);
334 
335     dilate(template, trowso2, tcolso2,
336            inputImage, dilatedImage, rows, cols, &dilatedTemplateCount);
337 
338     erode(template, trowso2, tcolso2,
339           inputImage, erodedImage, rows, cols, templateCountP);
340 
341     subtract(dilatedImage, erodedImage, outputImage, rows, cols);
342 
343     pgm_freearray(erodedImage, rows );
344     pgm_freearray(dilatedImage, rows );
345 }
346 
347 
348 
349 int
main(int argc,const char ** argv)350 main(int argc, const char ** argv) {
351 
352     struct CmdlineInfo cmdline;
353     FILE * ifP;
354     bit ** template;
355     unsigned int templateCols, templateRows;
356     int cols, rows;
357     gray maxval;
358     gray ** inputImage;
359     gray ** outputImage;
360     unsigned int templateCount;
361 
362     pm_proginit(&argc, argv);
363 
364     parseCommandLine(argc, argv, &cmdline);
365 
366     ifP = pm_openr(cmdline.inputFileName);
367 
368     readTemplateMatrix(cmdline.templateFileName,
369                        &template, &templateRows, &templateCols);
370 
371     /* Template coords run from -templateCols/2 .. 0 .. + templateCols/2 */
372 
373     inputImage = pgm_readpgm(ifP, &cols, &rows, &maxval);
374 
375     if (cols < templateCols || rows < templateRows)
376         pm_error("the image is smaller than the convolution matrix" );
377 
378     outputImage = pgm_allocarray(cols, rows);
379 
380     switch (cmdline.operation) {
381     case DILATE:
382         dilate(template, templateRows/2, templateCols/2,
383                inputImage, outputImage, rows, cols,
384                &templateCount);
385         break;
386     case ERODE:
387         erode(template, templateRows/2, templateCols/2,
388               inputImage, outputImage, rows, cols,
389               &templateCount);
390         break;
391     case OPEN:
392         openMorph(template, templateRows/2, templateCols/2,
393               inputImage, outputImage, rows, cols,
394               &templateCount);
395         break;
396     case CLOSE:
397         closeMorph(template, templateRows/2, templateCols/2,
398                    inputImage, outputImage, rows, cols,
399                    &templateCount);
400         break;
401     case GRADIENT:
402         gradient(template, templateRows/2, templateCols/2,
403                  inputImage, outputImage, rows, cols,
404                  &templateCount);
405         break;
406     }
407 
408     if (templateCount == 0)
409         pm_error( "The template was empty!" );
410 
411     pgm_writepgm(stdout, outputImage, cols, rows, maxval, 0);
412 
413     pgm_freearray(outputImage, rows);
414     pgm_freearray(inputImage, rows);
415     pm_close(ifP);
416 
417     return 0;
418 }
419 
420