Add export format options: CSV, JSON, PPM
[gencolormap.git] / cmdline.cpp
1 /*
2  * Copyright (C) 2015, 2016, 2017, 2018, 2019
3  * Computer Graphics Group, University of Siegen
4  * Written by Martin Lambers <martin.lambers@uni-siegen.de>
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22  * SOFTWARE.
23  */
24
25 #include <vector>
26 #include <cstdio>
27 #include <cstdlib>
28 #include <cstring>
29 #include <cmath>
30
31 #include <getopt.h>
32 extern char *optarg;
33 extern int optind;
34
35 #include "colormap.hpp"
36 #include "export.hpp"
37
38 enum type {
39     brewer_seq,
40     brewer_div,
41     brewer_qual,
42     plseq_lightness,
43     plseq_saturation,
44     plseq_rainbow,
45     plseq_blackbody,
46     pldiv_lightness,
47     pldiv_saturation,
48     plqual_hue,
49     cubehelix,
50     moreland,
51     mcnames
52 };
53
54 enum format {
55     csv,
56     json,
57     ppm
58 };
59
60 int main(int argc, char* argv[])
61 {
62     bool print_version = false;
63     bool print_help = false;
64     int format = csv;
65     int type = brewer_seq;
66     int n = 256;
67     float hue = -1.0f;
68     float divergence = -1.0f;
69     float contrast = -1.0f;
70     float saturation = -1.0f;
71     float brightness = -1.0f;
72     float warmth = -1.0f;
73     float lightness = -1.0f;
74     float rotations = NAN;
75     float temperature = -1.0f;
76     float range = -1.0f;
77     float gamma = -1.0f;
78     bool have_color0 = false;
79     unsigned char color0[3];
80     bool have_color1 = false;
81     unsigned char color1[3];
82     float periods = NAN;
83     struct option options[] = {
84         { "version",     no_argument,       0, 'v' },
85         { "help",        no_argument,       0, 'H' },
86         { "format",      required_argument, 0, 'f' },
87         { "type",        required_argument, 0, 't' },
88         { "n",           required_argument, 0, 'n' },
89         { "hue",         required_argument, 0, 'h' },
90         { "divergence",  required_argument, 0, 'd' },
91         { "contrast",    required_argument, 0, 'c' },
92         { "saturation",  required_argument, 0, 's' },
93         { "brightness",  required_argument, 0, 'b' },
94         { "warmth",      required_argument, 0, 'w' },
95         { "lightness",   required_argument, 0, 'l' },
96         { "rotations",   required_argument, 0, 'r' },
97         { "temperature", required_argument, 0, 'T' },
98         { "range",       required_argument, 0, 'R' },
99         { "gamma",       required_argument, 0, 'g' },
100         { "color0",      required_argument, 0, 'A' },
101         { "color1",      required_argument, 0, 'O' },
102         { "periods",     required_argument, 0, 'p' },
103         { 0, 0, 0, 0 }
104     };
105
106     for (;;) {
107         int c = getopt_long(argc, argv, "vHf:t:n:h:d:c:s:b:w:l:T:R:r:g:A:O:p:", options, NULL);
108         if (c == -1)
109             break;
110         switch (c) {
111         case 'v':
112             print_version = true;
113             break;
114         case 'H':
115             print_help = true;
116             break;
117         case 'f':
118             format = (strcmp(optarg, "csv") == 0 ? csv
119                     : strcmp(optarg, "json") == 0 ? json
120                     : strcmp(optarg, "ppm") == 0 ? ppm
121                     : -1);
122             break;
123         case 't':
124             type = (strcmp(optarg, "brewer-sequential") == 0 ? brewer_seq
125                     : strcmp(optarg, "brewer-diverging") == 0 ? brewer_div
126                     : strcmp(optarg, "brewer-qualitative") == 0 ? brewer_qual
127                     : strcmp(optarg, "plsequential-lightness") == 0 ? plseq_lightness
128                     : strcmp(optarg, "plsequential-saturation") == 0 ? plseq_saturation
129                     : strcmp(optarg, "plsequential-rainbow") == 0 ? plseq_rainbow
130                     : strcmp(optarg, "plsequential-blackbody") == 0 ? plseq_blackbody
131                     : strcmp(optarg, "pldiverging-lightness") == 0 ? pldiv_lightness
132                     : strcmp(optarg, "pldiverging-saturation") == 0 ? pldiv_saturation
133                     : strcmp(optarg, "plqualitative-hue") == 0 ? plqual_hue
134                     : strcmp(optarg, "cubehelix") == 0 ? cubehelix
135                     : strcmp(optarg, "moreland") == 0 ? moreland
136                     : strcmp(optarg, "mcnames") == 0 ? mcnames
137                     : -1);
138             break;
139         case 'n':
140             n = atoi(optarg);
141             break;
142         case 'h':
143             hue = atof(optarg) * M_PI / 180.0;
144             break;
145         case 'd':
146             divergence = atof(optarg) * M_PI / 180.0;
147             break;
148         case 'c':
149             contrast = atof(optarg);
150             break;
151         case 's':
152             saturation = atof(optarg);
153             break;
154         case 'b':
155             brightness = atof(optarg);
156             break;
157         case 'w':
158             warmth = atof(optarg);
159             break;
160         case 'l':
161             lightness = atof(optarg);
162             break;
163         case 'r':
164             rotations = atof(optarg);
165             break;
166         case 'T':
167             temperature = atof(optarg);
168             break;
169         case 'R':
170             range = atof(optarg);
171             break;
172         case 'g':
173             gamma = atof(optarg);
174             break;
175         case 'A':
176             std::sscanf(optarg, "%hhu,%hhu,%hhu", color0 + 0, color0 + 1, color0 + 2);
177             have_color0 = true;
178             break;
179         case 'O':
180             std::sscanf(optarg, "%hhu,%hhu,%hhu", color1 + 0, color1 + 1, color1 + 2);
181             have_color1 = true;
182             break;
183         case 'p':
184             periods = atof(optarg);
185             break;
186         default:
187             return 1;
188         }
189     }
190
191     if (print_version) {
192         printf("gencolormap version 1.0\n"
193                 "https://marlam.de/gencolormap\n"
194                 "Copyright (C) 2019 Computer Graphics Group, University of Siegen.\n"
195                 "Written by Martin Lambers <martin.lambers@uni-siegen.de>.\n"
196                 "This is free software under the terms of the MIT/Expat License.\n"
197                 "There is NO WARRANTY, to the extent permitted by law.\n");
198         return 0;
199     }
200
201     if (print_help) {
202         printf("Usage: %s [option...]\n"
203                 "Generates a color map and prints it to standard output.\n"
204                 "Prints the number of colors that had to be clipped to standard error.\n"
205                 "Common options:\n"
206                 "  [-f|--format=csv|json|ppm]          Set output format\n"
207                 "  [-n|--n=N]                          Set number of colors in the map\n"
208                 "Brewer-like color maps:\n"
209                 "  [-t|--type=brewer-sequential]       Generate a sequential color map\n"
210                 "  [-t|--type=brewer-diverging]        Generate a diverging color map\n"
211                 "  [-t|--type=brewer-qualitative]      Generate a qualitative color map\n"
212                 "  [-h|--hue=H]                        Set default hue in [0,360] degrees\n"
213                 "  [-c|--contrast=C]                   Set contrast in [0,1]\n"
214                 "  [-s|--saturation=S]                 Set saturation in [0,1]\n"
215                 "  [-b|--brightness=B]                 Set brightness in [0,1]\n"
216                 "  [-w|--warmth=W]                     Set warmth in [0,1] for seq. and div. maps\n"
217                 "  [-d|--divergence=D]                 Set diverg. in deg for div. and qual. maps\n"
218                 "Perceptually linear color maps:\n"
219                 "  [-t|--type=plsequential-lightness]  Sequential map, varying lightness\n"
220                 "  [-t|--type=plsequential-saturation] Sequential map, varying saturation\n"
221                 "  [-t|--type=plsequential-rainbow]    Sequential map, varying hue (rainbow)\n"
222                 "  [-t|--type=plsequential-blackbody]  Sequential map, varying hue (black body)\n"
223                 "  [-t|--type=pldiverging-lightness]   Diverging map, varying lightness\n"
224                 "  [-t|--type=pldiverging-saturation]  Diverging map, varying saturation\n"
225                 "  [-t|--type=plqualitative-hue]       Qualitative map, evenly distributed hue\n"
226                 "  [-l|--lightness=L]                  Set lightness in [0,1]\n"
227                 "  [-s|--saturation=S]                 Set saturation in [0,1]\n"
228                 "  [-h|--hue=H]                        Set default hue in [0,360] degrees\n"
229                 "  [-d|--divergence=D]                 Set diverg. in deg for div. and qual. maps\n"
230                 "  [-r|--rotations=R]                  Set number of rotations for rainbow maps\n"
231                 "  [-T|--temperature=T]                Set start temp. in K for black body maps\n"
232                 "  [-R|--range=R]                      Set temp. range in K for black body maps\n"
233                 "CubeHelix color maps:\n"
234                 "  [-t|--type=cubehelix]               Generate a CubeHelix color map\n"
235                 "  [-h|--hue=H]                        Set start hue in [0,180] degrees\n"
236                 "  [-r|--rotations=R]                  Set number of rotations, in (-infty,infty)\n"
237                 "  [-s|--saturation=S]                 Set saturation, in [0,1]\n"
238                 "  [-g|--gamma=G]                      Set gamma correction, in (0,infty)\n"
239                 "Moreland diverging color maps:\n"
240                 "  [-t|--type=moreland]                Generate a Moreland diverging color map\n"
241                 "  [-A|--color0=sr,sg,sb]              Set the first color as sRGB in [0,255]\n"
242                 "  [-O|--color1=sr,sg,sb]              Set the last color as sRGB in [0,255]\n"
243                 "McNames sequential color maps:\n"
244                 "  [-t|--type=mcnames]                 Generate a McNames sequential color map\n"
245                 "  [-p|--periods=P]                    Set the number of periods in (0, infty)\n"
246                 "Defaults: format=csv, n=256, type=brewer-sequential\n"
247                 "https://marlam.de/gencolormap\n", argv[0]);
248         return 0;
249     }
250
251     if (format < 0) {
252         fprintf(stderr, "Invalid argument for option -f|--format.\n");
253         return 1;
254     }
255     if (n < 2) {
256         fprintf(stderr, "Invalid argument for option -n|--n.\n");
257         return 1;
258     }
259     if (type < 0) {
260         fprintf(stderr, "Invalid argument for option -t|--type.\n");
261         return 1;
262     }
263     if (hue < 0.0f) {
264         if (type == brewer_seq)
265             hue = ColorMap::BrewerSequentialDefaultHue;
266         else if (type == brewer_div)
267             hue = ColorMap::BrewerDivergingDefaultHue;
268         else if (type == brewer_qual)
269             hue = ColorMap::BrewerQualitativeDefaultHue;
270         else if (type == plseq_lightness)
271             hue = ColorMap::PLSequentialLightnessDefaultHue;
272         else if (type == plseq_saturation)
273             hue = ColorMap::PLSequentialSaturationDefaultHue;
274         else if (type == plseq_rainbow)
275             hue = ColorMap::PLSequentialRainbowDefaultHue;
276         else if (type == pldiv_lightness)
277             hue = ColorMap::PLDivergingLightnessDefaultHue;
278         else if (type == pldiv_saturation)
279             hue = ColorMap::PLDivergingSaturationDefaultHue;
280         else if (type == plqual_hue)
281             hue = ColorMap::PLQualitativeHueDefaultHue;
282         else if (type == cubehelix)
283             hue = ColorMap::CubeHelixDefaultHue;
284     }
285     if (divergence < 0.0f) {
286         if (type == brewer_div)
287             divergence = ColorMap::BrewerDivergingDefaultDivergence;
288         else if (type == brewer_qual)
289             divergence = ColorMap::BrewerQualitativeDefaultDivergence;
290         else if (type == pldiv_lightness)
291             divergence = ColorMap::PLDivergingLightnessDefaultDivergence;
292         else if (type == pldiv_saturation)
293             divergence = ColorMap::PLDivergingSaturationDefaultDivergence;
294         else if (type == plqual_hue)
295             divergence = ColorMap::PLQualitativeHueDefaultDivergence;
296     }
297     if (contrast < 0.0f) {
298         if (type == brewer_seq)
299             contrast = (n <= 9 ? ColorMap::BrewerSequentialDefaultContrastForSmallN(n)
300                     : ColorMap::BrewerSequentialDefaultContrast);
301         else if (type == brewer_div)
302             contrast = (n <= 9 ? ColorMap::BrewerDivergingDefaultContrastForSmallN(n)
303                     : ColorMap::BrewerDivergingDefaultContrast);
304         else if (type == brewer_qual)
305             contrast = ColorMap::BrewerQualitativeDefaultContrast;
306     }
307     if (saturation < 0.0f) {
308         if (type == brewer_seq)
309             saturation = ColorMap::BrewerSequentialDefaultSaturation;
310         else if (type == brewer_div)
311             saturation = ColorMap::BrewerDivergingDefaultSaturation;
312         else if (type == brewer_qual)
313             saturation = ColorMap::BrewerQualitativeDefaultSaturation;
314         else if (type == plseq_lightness)
315             saturation = ColorMap::PLSequentialLightnessDefaultSaturation;
316         else if (type == plseq_saturation)
317             saturation = ColorMap::PLSequentialSaturationDefaultSaturation;
318         else if (type == plseq_rainbow)
319             saturation = ColorMap::PLSequentialRainbowDefaultSaturation;
320         else if (type == plseq_blackbody)
321             saturation = ColorMap::PLSequentialBlackBodyDefaultSaturation;
322         else if (type == pldiv_lightness)
323             saturation = ColorMap::PLDivergingLightnessDefaultSaturation;
324         else if (type == pldiv_saturation)
325             saturation = ColorMap::PLDivergingSaturationDefaultSaturation;
326         else if (type == plqual_hue)
327             saturation = ColorMap::PLQualitativeHueDefaultSaturation;
328         else if (type == cubehelix)
329             saturation = ColorMap::CubeHelixDefaultSaturation;
330     }
331     if (brightness < 0.0f) {
332         if (type == brewer_seq)
333             brightness = ColorMap::BrewerSequentialDefaultBrightness;
334         else if (type == brewer_div)
335             brightness = ColorMap::BrewerDivergingDefaultBrightness;
336         else if (type == brewer_qual)
337             brightness = ColorMap::BrewerQualitativeDefaultBrightness;
338     }
339     if (warmth < 0.0f) {
340         if (type == brewer_seq)
341             warmth = ColorMap::BrewerSequentialDefaultWarmth;
342         else if (type == brewer_div)
343             warmth = ColorMap::BrewerDivergingDefaultWarmth;
344     }
345     if (lightness < 0.0f) {
346         if (type == plseq_saturation)
347             lightness = ColorMap::PLSequentialSaturationDefaultLightness;
348         else if (type == pldiv_lightness)
349             lightness = ColorMap::PLDivergingLightnessDefaultLightness;
350         else if (type == pldiv_saturation)
351             lightness = ColorMap::PLDivergingSaturationDefaultLightness;
352         else if (type == plqual_hue)
353             lightness = ColorMap::PLQualitativeHueDefaultLightness;
354     }
355     if (std::isnan(rotations)) {
356         if (type == plseq_rainbow)
357             rotations = ColorMap::PLSequentialRainbowDefaultRotations;
358         else if (type == cubehelix)
359             rotations = ColorMap::CubeHelixDefaultRotations;
360     }
361     if (temperature < 0.0f) {
362         if (type == plseq_blackbody)
363             temperature = ColorMap::PLSequentialBlackBodyDefaultTemperature;
364     }
365     if (range < 0.0f) {
366         if (type == plseq_blackbody)
367             range = ColorMap::PLSequentialBlackBodyDefaultRange;
368     }
369     if (gamma < 0.0f) {
370         if (type == cubehelix)
371             gamma = ColorMap::CubeHelixDefaultGamma;
372     }
373     if (!have_color0) {
374         if (type == moreland) {
375             color0[0] = ColorMap::MorelandDefaultR0;
376             color0[1] = ColorMap::MorelandDefaultG0;
377             color0[2] = ColorMap::MorelandDefaultB0;
378         }
379     }
380     if (!have_color1) {
381         if (type == moreland) {
382             color1[0] = ColorMap::MorelandDefaultR1;
383             color1[1] = ColorMap::MorelandDefaultG1;
384             color1[2] = ColorMap::MorelandDefaultB1;
385         }
386     }
387     if (std::isnan(periods)) {
388         if (type == mcnames)
389             periods = ColorMap::McNamesDefaultPeriods;
390     }
391
392     std::vector<unsigned char> colormap(3 * n);
393     int clipped;
394     switch (type) {
395     case brewer_seq:
396         clipped = ColorMap::BrewerSequential(n, &(colormap[0]), hue, contrast, saturation, brightness, warmth);
397         break;
398     case brewer_div:
399         clipped = ColorMap::BrewerDiverging(n, &(colormap[0]), hue, divergence, contrast, saturation, brightness, warmth);
400         break;
401     case brewer_qual:
402         clipped = ColorMap::BrewerQualitative(n, &(colormap[0]), hue, divergence, contrast, saturation, brightness);
403         break;
404     case plseq_lightness:
405         clipped = ColorMap::PLSequentialLightness(n, &(colormap[0]), saturation, hue);
406         break;
407     case plseq_saturation:
408         clipped = ColorMap::PLSequentialSaturation(n, &(colormap[0]), lightness, saturation, hue);
409         break;
410     case plseq_rainbow:
411         clipped = ColorMap::PLSequentialRainbow(n, &(colormap[0]), hue, rotations, saturation);
412         break;
413     case plseq_blackbody:
414         clipped = ColorMap::PLSequentialBlackBody(n, &(colormap[0]), temperature, range, saturation);
415         break;
416     case pldiv_lightness:
417         clipped = ColorMap::PLDivergingLightness(n, &(colormap[0]), lightness, saturation, hue, divergence);
418         break;
419     case pldiv_saturation:
420         clipped = ColorMap::PLDivergingSaturation(n, &(colormap[0]), lightness, saturation, hue, divergence);
421         break;
422     case plqual_hue:
423         clipped = ColorMap::PLQualitativeHue(n, &(colormap[0]), hue, divergence, lightness, saturation);
424         break;
425     case cubehelix:
426         clipped = ColorMap::CubeHelix(n, &(colormap[0]), hue, rotations, saturation, gamma);
427         break;
428     case moreland:
429         clipped = ColorMap::Moreland(n, &(colormap[0]),
430                 color0[0], color0[1], color0[2],
431                 color1[0], color1[1], color1[2]);
432         break;
433     case mcnames:
434         clipped = ColorMap::McNames(n, &(colormap[0]), periods);
435         break;
436     }
437
438     std::string output;
439     if (format == csv) {
440         output = ColorMap::ToCSV(n, colormap.data());
441     } else if (format == json) {
442         output = ColorMap::ToJSON(n, colormap.data());
443     } else {
444         output = ColorMap::ToPPM(n, colormap.data());
445     }
446     fputs(output.c_str(), stdout);
447     fprintf(stderr, "%d color(s) were clipped\n", clipped);
448
449     return 0;
450 }