1 /*=========================================================================
2                              ppmcolormask
3 ===========================================================================
4 
5   This program produces a PBM mask of areas containing a certain color.
6 
7   By Bryan Henderson, Olympia WA; April 2000.
8 
9   Contributed to the public domain by its author.
10 =========================================================================*/
11 
12 #define _DEFAULT_SOURCE /* New name for SVID & BSD source defines */
13 #define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
14 #define _BSD_SOURCE  /* Make sure strdup() is in <string.h> */
15 #include <assert.h>
16 #include <string.h>
17 
18 #include "pm_c_util.h"
19 #include "shhopt.h"
20 #include "mallocvar.h"
21 #include "nstring.h"
22 #include "ppm.h"
23 #include "pam.h"
24 
25 typedef enum {
26     MATCH_EXACT,
27     MATCH_BK
28 } MatchType;
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;
35     unsigned int colorCt;
36     struct {
37         MatchType matchType;
38         union {
39             tuplen   color;   /* matchType == MATCH_EXACT */
40             bk_color bkColor; /* matchType == MATCH_BK */
41         } u;
42     } maskColor[16];
43     unsigned int verbose;
44 };
45 
46 
47 
48 static void
freeCmdline(struct CmdlineInfo * const cmdlineP)49 freeCmdline(struct CmdlineInfo * const cmdlineP) {
50 
51     unsigned int i;
52 
53     for (i = 0; i < cmdlineP->colorCt; ++ i) {
54         if (cmdlineP->maskColor[i].matchType == MATCH_EXACT)
55             free(cmdlineP->maskColor[i].u.color);
56     }
57 }
58 
59 
60 
61 static void
parseColorOpt(const char * const colorOpt,struct CmdlineInfo * const cmdlineP)62 parseColorOpt(const char *         const colorOpt,
63               struct CmdlineInfo * const cmdlineP) {
64 
65     unsigned int colorCt;
66     char * colorOptWork;
67     char * cursor;
68     bool eol;
69 
70     colorOptWork = strdup(colorOpt);
71     cursor = &colorOptWork[0];
72 
73     eol = FALSE;    /* initial value */
74     colorCt = 0;    /* initial value */
75     while (!eol && colorCt < ARRAY_SIZE(cmdlineP->maskColor)) {
76         const char * token;
77         token = pm_strsep(&cursor, ",");
78         if (token) {
79             if (strneq(token, "bk:", 3)) {
80                 cmdlineP->maskColor[colorCt].matchType = MATCH_BK;
81                 cmdlineP->maskColor[colorCt].u.bkColor =
82                     ppm_bk_color_from_name(&token[3]);
83             } else {
84                 cmdlineP->maskColor[colorCt].matchType = MATCH_EXACT;
85                 cmdlineP->maskColor[colorCt].u.color =
86                     pnm_parsecolorn(token);
87             }
88             ++colorCt;
89         } else
90             eol = TRUE;
91     }
92     free(colorOptWork);
93 
94     cmdlineP->colorCt = colorCt;
95 }
96 
97 
98 
99 static void
parseCommandLine(int argc,const char ** argv,struct CmdlineInfo * cmdlineP)100 parseCommandLine(int argc, const char ** argv,
101                  struct CmdlineInfo *cmdlineP) {
102 /*----------------------------------------------------------------------------
103    Note that many of the strings that this function returns in the
104    *cmdlineP structure are actually in the supplied argv array.  And
105    sometimes, one of these strings is actually just a suffix of an entry
106    in argv!
107 -----------------------------------------------------------------------------*/
108     optEntry * option_def;
109         /* Instructions to OptParseOptions3 on how to parse our options. */
110     optStruct3 opt;
111 
112     unsigned int option_def_index;
113     const char * colorOpt;
114     unsigned int colorSpec;
115 
116     MALLOCARRAY_NOFAIL(option_def, 100);
117 
118     option_def_index = 0;   /* incremented by OPTENT3 */
119     OPTENT3(0, "color",      OPT_STRING, &colorOpt, &colorSpec,           0);
120     OPTENT3(0, "verbose",    OPT_FLAG,   NULL, &cmdlineP->verbose,        0);
121 
122     opt.opt_table = option_def;
123     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
124     opt.allowNegNum = FALSE;  /* We may have parms that are negative numbers */
125 
126     pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
127         /* Uses and sets argc, argv, and all of *cmdlineP. */
128 
129     if (colorSpec)
130         parseColorOpt(colorOpt, cmdlineP);
131 
132     if (colorSpec) {
133         if (argc-1 < 1)
134             cmdlineP->inputFilename = "-";  /* he wants stdin */
135         else if (argc-1 == 1)
136             cmdlineP->inputFilename = argv[1];
137         else
138             pm_error("Too many arguments.  When you specify -color, "
139                      "the only argument accepted is the optional input "
140                      "file name.");
141     } else {
142         if (argc-1 < 1)
143             pm_error("You must specify the -color option.");
144         else {
145             cmdlineP->colorCt = 1;
146             cmdlineP->maskColor[0].matchType = MATCH_EXACT;
147             cmdlineP->maskColor[0].u.color = pnm_parsecolorn(argv[1]);
148 
149             if (argc - 1 < 2)
150                 cmdlineP->inputFilename = "-";  /* he wants stdin */
151             else if (argc-1 == 2)
152                 cmdlineP->inputFilename = argv[2];
153             else
154                 pm_error("Too many arguments.  The only arguments accepted "
155                          "are the mask color and optional input file name");
156         }
157     }
158 }
159 
160 
161 
162 static void
setupOutput(FILE * const fileP,unsigned int const width,unsigned int const height,struct pam * const outPamP)163 setupOutput(FILE *       const fileP,
164             unsigned int const width,
165             unsigned int const height,
166             struct pam * const outPamP) {
167 
168     outPamP->size             = sizeof(*outPamP);
169     outPamP->len              = PAM_STRUCT_SIZE(tuple_type);
170     outPamP->file             = fileP;
171     outPamP->format           = RPBM_FORMAT;
172     outPamP->plainformat      = 0;
173     outPamP->height           = height;
174     outPamP->width            = width;
175     outPamP->depth            = 1;
176     outPamP->maxval           = 1;
177     outPamP->bytes_per_sample = 1;
178     strcpy(outPamP->tuple_type, PAM_PBM_TUPLETYPE);
179 }
180 
181 
182 
183 static bool
isBkColor(tuple const comparator,struct pam * const pamP,bk_color const comparand)184 isBkColor(tuple        const comparator,
185           struct pam * const pamP,
186           bk_color     const comparand) {
187 
188     pixel comparatorPixel;
189     bk_color comparatorBk;
190 
191     /* TODO: keep a cache of the bk color for each color in
192        a colorhash_table.
193     */
194 
195     assert(pamP->depth >= 3);
196 
197     PPM_ASSIGN(comparatorPixel,
198                comparator[PAM_RED_PLANE],
199                comparator[PAM_GRN_PLANE],
200                comparator[PAM_BLU_PLANE]);
201 
202     comparatorBk = ppm_bk_color_from_color(comparatorPixel, pamP->maxval);
203 
204     return comparatorBk == comparand;
205 }
206 
207 
208 
209 static bool
colorIsInSet(tuple const color,struct pam * const pamP,struct CmdlineInfo const cmdline)210 colorIsInSet(tuple              const color,
211              struct pam *       const pamP,
212              struct CmdlineInfo const cmdline) {
213 
214     bool isInSet;
215     unsigned int i;
216     tuple maskColorUnnorm;
217 
218     maskColorUnnorm = pnm_allocpamtuple(pamP);
219 
220     for (i = 0, isInSet = FALSE; i < cmdline.colorCt && !isInSet; ++i) {
221 
222         assert(i < ARRAY_SIZE(cmdline.maskColor));
223 
224         switch(cmdline.maskColor[i].matchType) {
225         case MATCH_EXACT:
226             pnm_unnormalizetuple(pamP,
227                                  cmdline.maskColor[i].u.color,
228                                  maskColorUnnorm);
229             if (pnm_tupleequal(pamP, color, maskColorUnnorm))
230                 isInSet = TRUE;
231             break;
232         case MATCH_BK:
233             if (isBkColor(color, pamP, cmdline.maskColor[i].u.bkColor))
234                 isInSet = TRUE;
235             break;
236         }
237     }
238 
239     free(maskColorUnnorm);
240 
241     return isInSet;
242 }
243 
244 
245 
246 int
main(int argc,const char * argv[])247 main(int argc, const char *argv[]) {
248 
249     struct CmdlineInfo cmdline;
250 
251     FILE * ifP;
252     struct pam inPam;
253     struct pam outPam;
254 
255     pm_proginit(&argc, argv);
256 
257     parseCommandLine(argc, argv, &cmdline);
258 
259     ifP = pm_openr(cmdline.inputFilename);
260 
261     pnm_readpaminit(ifP, &inPam, PAM_STRUCT_SIZE(allocation_depth));
262 
263     pnm_setminallocationdepth(&inPam, 3);
264 
265     setupOutput(stdout, inPam.width, inPam.height, &outPam);
266 
267     pnm_writepaminit(&outPam);
268     {
269         tuple * const inputRow = pnm_allocpamrow(&inPam);
270         tuple * const maskRow  = pnm_allocpamrow(&outPam);
271 
272         unsigned int numPixelsMasked;
273 
274         unsigned int row;
275 
276         for (row = 0, numPixelsMasked = 0; row < inPam.height; ++row) {
277             unsigned int col;
278             pnm_readpamrow(&inPam, inputRow);
279             pnm_makerowrgb(&inPam, inputRow);
280             for (col = 0; col < inPam.width; ++col) {
281                 if (colorIsInSet(inputRow[col], &inPam, cmdline)) {
282                     maskRow[col][0] = PAM_BLACK;
283                     ++numPixelsMasked;
284                 } else
285                     maskRow[col][0] = PAM_BW_WHITE;
286             }
287             pnm_writepamrow(&outPam, maskRow);
288         }
289 
290         if (cmdline.verbose)
291             pm_message("%u pixels found matching %u requested colors",
292                        numPixelsMasked, cmdline.colorCt);
293 
294         pnm_freepamrow(maskRow);
295         pnm_freepamrow(inputRow);
296     }
297     freeCmdline(&cmdline);
298     pm_close(ifP);
299 
300     return 0;
301 }
302 
303 
304 
305