You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

main.c 17 KiB

21 years ago
21 years ago
21 years ago
21 years ago
21 years ago
19 years ago
21 years ago
21 years ago
19 years ago
21 years ago
21 years ago
21 years ago
21 years ago
21 years ago
21 years ago
19 years ago
21 years ago
21 years ago
21 years ago
21 years ago
21 years ago
21 years ago
21 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605
  1. /*
  2. * This file is part of Aptdec.
  3. * Copyright (c) 2004-2009 Thierry Leconte (F4DWV), Xerbo (xerbo@protonmail.com) 2019
  4. *
  5. * Aptdec is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 2 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. *
  18. */
  19. #include <stdlib.h>
  20. #include <stdio.h>
  21. #include <string.h>
  22. #include <getopt.h>
  23. #include <libgen.h>
  24. #include <math.h>
  25. #include <sndfile.h>
  26. #include <png.h>
  27. #include "messages.h"
  28. #include "offsets.h"
  29. #include "palette.h"
  30. extern int getpixelrow(float *pixelv, int nrow, int *zenith);
  31. extern int init_dsp(double F);
  32. static SNDFILE *inwav;
  33. static int initsnd(char *filename) {
  34. SF_INFO infwav;
  35. int res;
  36. // Open audio file
  37. infwav.format = 0;
  38. inwav = sf_open(filename, SFM_READ, &infwav);
  39. if (inwav == NULL) {
  40. fprintf(stderr, ERR_FILE_READ, filename);
  41. return(0);
  42. }
  43. res = init_dsp(infwav.samplerate);
  44. printf("Input file: %s\n", filename);
  45. if(res < 0) {
  46. fprintf(stderr, "Input sample rate too low: %d\n", infwav.samplerate);
  47. return(0);
  48. }else if(res > 0) {
  49. fprintf(stderr, "Input sample rate too high: %d\n", infwav.samplerate);
  50. return(0);
  51. }
  52. printf("Input sample rate: %d\n", infwav.samplerate);
  53. if (infwav.channels != 1) {
  54. fprintf(stderr, "Too many channels in input file: %d\n", infwav.channels);
  55. return(0);
  56. }
  57. return(1);
  58. }
  59. // Get samples from the wave file
  60. int getsample(float *sample, int nb) {
  61. return sf_read_float(inwav, sample, nb);
  62. }
  63. static png_text text_ptr[] = {
  64. {PNG_TEXT_COMPRESSION_NONE, "Software", VERSION},
  65. {PNG_TEXT_COMPRESSION_NONE, "Channel", NULL, 0},
  66. {PNG_TEXT_COMPRESSION_NONE, "Description", "NOAA satellite image", 20}
  67. };
  68. int mapOverlay(char *filename, float **crow, int nrow, int zenith, int MCIR) {
  69. FILE *fp = fopen(filename, "rb");
  70. // Create reader
  71. png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
  72. if(!png) return 0;
  73. png_infop info = png_create_info_struct(png);
  74. if(!info) return 0;
  75. png_init_io(png, fp);
  76. // Read info from header
  77. png_read_info(png, info);
  78. int width = png_get_image_width(png, info);
  79. int height = png_get_image_height(png, info);
  80. png_byte color_type = png_get_color_type(png, info);
  81. png_byte bit_depth = png_get_bit_depth(png, info);
  82. // Check the image
  83. if(width != 1040){
  84. fprintf(stderr, "Map must be 1040px wide.\n");
  85. return 0;
  86. }else if(bit_depth != 16){
  87. fprintf(stderr, "Map must be 16 bit color.\n");
  88. return 0;
  89. }else if(color_type != PNG_COLOR_TYPE_RGB){
  90. fprintf(stderr, "Map must be RGB.\n");
  91. return 0;
  92. }else if(zenith > height/2 || nrow-zenith > height/2){
  93. fprintf(stderr, "WARNING: Map is too short to cover entire image\n");
  94. }
  95. // Create row buffers
  96. png_bytep *mapRows = NULL;
  97. mapRows = (png_bytep *) malloc(sizeof(png_bytep) * height);
  98. for(int y = 0; y < height; y++) mapRows[y] = (png_byte *) malloc(png_get_rowbytes(png, info));
  99. // Read image
  100. png_read_image(png, mapRows);
  101. // Tidy up
  102. fclose(fp);
  103. png_destroy_read_struct(&png, &info, NULL);
  104. int mapOffset = (height/2)-zenith;
  105. for(int y = 0; y < nrow; y++) {
  106. for(int x = 49; x < width - 82; x++){
  107. // Maps are 16 bit / channel
  108. png_bytep px = &mapRows[CLIP(y + mapOffset, 0, height)][x * 6];
  109. uint16_t r = (uint16_t)(px[0] << 8) | px[1];
  110. uint16_t g = (uint16_t)(px[2] << 8) | px[3];
  111. uint16_t b = (uint16_t)(px[4] << 8) | px[5];
  112. // Pointers to the current pixel in each channel
  113. float *cha = &crow[y][(x+36) * 3];
  114. float *chb = &crow[y][(x+CHB_OFFSET-49) * 3];
  115. // Fill in map
  116. if(MCIR){
  117. // Sea
  118. cha[0] = 42; cha[1] = 53; cha[2] = 105;
  119. // Land
  120. if(g > 128){
  121. float vegetation = (r-160) / 96.0;
  122. cha[0] = 120; cha[1] = vegetation*60.0 + 100; cha[2] = 95;
  123. }
  124. }
  125. // Color -> alpha: composite
  126. int composite = r + g + b;
  127. // Color -> alpha: flattern color depth
  128. float factor = (255 * 255 * 2.0) / composite;
  129. r *= factor; g *= factor; b *= factor;
  130. // Color -> alpha: convert black to alpha
  131. float alpha = CLIP(abs(0 - composite) / 65535.0, 0, 1);
  132. // Map overlay on channel A
  133. cha[0] = MCOMPOSITE(r/257, alpha, cha[0], 1);
  134. cha[1] = MCOMPOSITE(g/257, alpha, cha[1], 1);
  135. cha[2] = MCOMPOSITE(b/257, alpha, cha[2], 1);
  136. // Map overlay on channel B
  137. if(!MCIR){
  138. chb[0] = MCOMPOSITE(r/257, alpha, chb[0], 1);
  139. chb[1] = MCOMPOSITE(g/257, alpha, chb[1], 1);
  140. chb[2] = MCOMPOSITE(b/257, alpha, chb[2], 1);
  141. }
  142. // Cloud overlay on channel A
  143. if(MCIR){
  144. float cloud = (chb[0] - 110) / 120;
  145. cha[0] = MCOMPOSITE(240, cloud, cha[0], 1);
  146. cha[1] = MCOMPOSITE(250, cloud, cha[1], 1);
  147. cha[2] = MCOMPOSITE(255, cloud, cha[2], 1);
  148. }
  149. }
  150. }
  151. return 1;
  152. }
  153. // Row where to satellite reaches peak elevation
  154. int zenith = 0;
  155. int applyPalette(float **crow, int nrow, int width, char *palette){
  156. if(palette == NULL) return 0;
  157. for(int y = 0; y < nrow; y++){
  158. for(int x = 0; x < width; x++){
  159. float *px = &crow[y][x * 3];
  160. px[0] = palette[(int)CLIP(px[0], 0, 255)*3 + 0];
  161. px[1] = palette[(int)CLIP(px[1], 0, 255)*3 + 1];
  162. px[2] = palette[(int)CLIP(px[2], 0, 255)*3 + 2];
  163. }
  164. }
  165. return 1;
  166. }
  167. static int ImageOut(char *filename, char *chid, float **prow, int nrow, int width, int offset, char *palette, char *effects, char *mapFile) {
  168. FILE *pngfile;
  169. // Reduce the width of the image to componsate for the missing telemetry
  170. if(CONTAINS(effects, 't')) width -= TOTAL_TELE;
  171. // Create writer
  172. png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
  173. if (!png_ptr) {
  174. png_destroy_write_struct(&png_ptr, (png_infopp) NULL);
  175. fprintf(stderr, ERR_PNG_WRITE);
  176. return(0);
  177. }
  178. png_infop info_ptr = png_create_info_struct(png_ptr);
  179. if (!info_ptr) {
  180. png_destroy_write_struct(&png_ptr, (png_infopp) NULL);
  181. fprintf(stderr, ERR_PNG_INFO);
  182. return(0);
  183. }
  184. // 8 bit RGB image
  185. png_set_IHDR(png_ptr, info_ptr, width, nrow,
  186. 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
  187. PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
  188. text_ptr[1].text = chid;
  189. text_ptr[1].text_length = strlen(chid);
  190. png_set_text(png_ptr, info_ptr, text_ptr, 3);
  191. png_set_pHYs(png_ptr, info_ptr, 4160, 4160, PNG_RESOLUTION_METER);
  192. // Init I/O
  193. pngfile = fopen(filename, "wb");
  194. if (!pngfile) {
  195. fprintf(stderr, ERR_FILE_WRITE, filename);
  196. return(1);
  197. }
  198. png_init_io(png_ptr, pngfile);
  199. png_write_info(png_ptr, info_ptr);
  200. // Move prow into crow, crow ~ color rows
  201. float *crow[3000];
  202. for(int i = 0; i < nrow; i++){
  203. crow[i] = (float *) malloc(sizeof(float) * 2080 * 3);
  204. for(int x = 0; x < 2080; x++)
  205. crow[i][x*3] = crow[i][x*3 + 1] = crow[i][x*3 + 2] = prow[i][x];
  206. }
  207. applyPalette(crow, nrow, 2080, palette);
  208. if(mapFile != NULL && mapFile[0] != '\0'){
  209. if(mapOverlay(mapFile, crow, nrow, zenith, strcmp(chid, "MCIR") == 0) == 0){
  210. fprintf(stderr, "Skipping MCIR generation; see above.\n");
  211. return 0;
  212. }
  213. }else if(strcmp(chid, "MCIR") == 0){
  214. fprintf(stderr, "Skipping MCIR generation; no map provided.\n");
  215. return 0;
  216. }
  217. extern void falsecolor(float vis, float temp, float *r, float *g, float *b);
  218. printf("Writing %s", filename);
  219. int fcimage = strcmp(chid, "False Color") == 0;
  220. // Build RGB image
  221. for (int n = 0; n < nrow; n++) {
  222. png_color pix[width];
  223. for (int i = 0; i < width; i++) {
  224. float *px = &crow[n][offset*3 + i*3];
  225. if(fcimage){
  226. float r = 0, g = 0, b = 0;
  227. falsecolor(prow[n][i + CHA_OFFSET], prow[n][i + CHB_OFFSET], &r, &g, &b);
  228. pix[i].red = r;
  229. pix[i].green = g;
  230. pix[i].blue = b;
  231. }else{
  232. pix[i].red = px[0];
  233. pix[i].green = px[1];
  234. pix[i].blue = px[2];
  235. }
  236. }
  237. png_write_row(png_ptr, (png_bytep) pix);
  238. }
  239. // Tidy up
  240. png_write_end(png_ptr, info_ptr);
  241. fclose(pngfile);
  242. printf("\nDone\n");
  243. png_destroy_write_struct(&png_ptr, &info_ptr);
  244. return(1);
  245. }
  246. // Outputs a image with the value distribution between channel A and B
  247. static void distrib(char *filename, float **prow, int nrow) {
  248. float *distrib[256];
  249. int max = 0;
  250. // Assign memory
  251. for(int i = 0; i < 256; i++)
  252. distrib[i] = (float *) malloc(sizeof(float) * 256);
  253. for(int n = 0; n < nrow; n++) {
  254. float *pixelv = prow[n];
  255. for(int i = 0; i < CH_WIDTH; i++) {
  256. int y = (int)(pixelv[i + CHA_OFFSET]);
  257. int x = (int)(pixelv[i + CHB_OFFSET]);
  258. distrib[y][x] += 1;
  259. if(distrib[y][x] > max) max = distrib[y][x];
  260. }
  261. }
  262. // Scale to 0-255
  263. for(int x = 0; x < 256; x++)
  264. for(int y = 0; y < 256; y++)
  265. distrib[y][x] = distrib[y][x] / max * 255;
  266. ImageOut(filename, "Brightness distribution", distrib, 256, 256, 0, NULL, 0, NULL);
  267. }
  268. extern int calibrate(float **prow, int nrow, int offset, int width);
  269. extern void histogramEqualise(float **prow, int nrow, int offset, int width);
  270. extern void temperature(float **prow, int nrow, int ch, int offset);
  271. extern int Ngvi(float **prow, int nrow);
  272. extern void readfcconf(char *file);
  273. extern int optind;
  274. extern char *optarg;
  275. // Default to NOAA 19
  276. int satnum = 4;
  277. static void usage(void) {
  278. printf("Aptdec [options] audio files ...\n"
  279. "Options:\n"
  280. " -e [t|h] Enhancements\n"
  281. " t: Crop telemetry\n"
  282. " h: Histogram equalise\n"
  283. " -i [r|a|b|c|t] Output image type\n"
  284. " r: Raw\n"
  285. " a: Channel A\n"
  286. " b: Channel B\n"
  287. " c: False color\n"
  288. " t: Temperature\n"
  289. " m: MCIR\n"
  290. " -d <dir> Image destination directory.\n"
  291. " -s [15-19] Satellite number\n"
  292. " -c <file> False color config file\n"
  293. " -m <file> Map file\n");
  294. exit(1);
  295. }
  296. int readRawImage(char *filename, float **prow, int *nrow) {
  297. FILE *fp = fopen(filename, "r");
  298. // Create reader
  299. png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
  300. if(!png) return 0;
  301. png_infop info = png_create_info_struct(png);
  302. if(!info) return 0;
  303. png_init_io(png, fp);
  304. // Read info from header
  305. png_read_info(png, info);
  306. int width = png_get_image_width(png, info);
  307. int height = png_get_image_height(png, info);
  308. png_byte color_type = png_get_color_type(png, info);
  309. png_byte bit_depth = png_get_bit_depth(png, info);
  310. // Check the image
  311. if(width != 2080){
  312. fprintf(stderr, "Raw image must be 2080px wide.\n");
  313. return 0;
  314. }else if(bit_depth != 8){
  315. fprintf(stderr, "Raw image must have 8 bit color.\n");
  316. return 0;
  317. }else if(color_type != PNG_COLOR_TYPE_GRAY){
  318. fprintf(stderr, "Raw image must be grayscale.\n");
  319. return 0;
  320. }
  321. // Create row buffers
  322. png_bytep *PNGrows = NULL;
  323. PNGrows = (png_bytep *) malloc(sizeof(png_bytep) * height);
  324. for(int y = 0; y < height; y++) PNGrows[y] = (png_byte *) malloc(png_get_rowbytes(png, info));
  325. // Read image
  326. png_read_image(png, PNGrows);
  327. // Tidy up
  328. fclose(fp);
  329. png_destroy_read_struct(&png, &info, NULL);
  330. // Put into prow
  331. *nrow = height;
  332. for(int y = 0; y < height; y++) {
  333. prow[y] = (float *) malloc(sizeof(float) * width);
  334. for(int x = 0; x < width; x++)
  335. prow[y][x] = (float)PNGrows[y][x];
  336. }
  337. return 1;
  338. }
  339. int main(int argc, char **argv) {
  340. char pngfilename[1024];
  341. char name[128];
  342. char pngdirname[128] = "";
  343. char mapFile[256];
  344. char *extension;
  345. // Default to a raw image, with no enhancements
  346. char imgopt[20] = "r";
  347. char enhancements[20] = "";
  348. // Image buffer
  349. float *prow[3000];
  350. int nrow;
  351. // Mapping between telemetry wedge value and channel
  352. static struct {
  353. char *id[7];
  354. char *name[7];
  355. } ch = {
  356. { "?", "1", "2", "3A", "4", "5", "3B" },
  357. { "unknown", "visble", "near-infrared", "mid-infrared", "thermal-infrared", "thermal-infrared", "mid-infrared" }
  358. };
  359. // The active sensor in each channel
  360. int chA, chB;
  361. // Print version
  362. printf(VERSION"\n");
  363. // Print usage if there are no arguments
  364. if(argc == 1)
  365. usage();
  366. int c;
  367. while ((c = getopt(argc, argv, "c:m:d:i:s:e:")) != EOF) {
  368. switch (c) {
  369. // Output directory name
  370. case 'd':
  371. strcpy(pngdirname, optarg);
  372. break;
  373. // False color config file
  374. case 'c':
  375. readfcconf(optarg);
  376. break;
  377. // Map file
  378. case 'm':
  379. strcpy(mapFile, optarg);
  380. break;
  381. // Output image type
  382. case 'i':
  383. strncpy(imgopt, optarg, 20);
  384. break;
  385. // Satellite number (for calibration)
  386. case 's':
  387. satnum = atoi(optarg)-15;
  388. // Check if it's within the valid range
  389. if (satnum < 0 || satnum > 4) {
  390. fprintf(stderr, "Invalid satellite number, it must be the range [15-19]\n");
  391. exit(1);
  392. }
  393. break;
  394. // Enchancements
  395. case 'e':
  396. strncpy(enhancements, optarg, 20);
  397. break;
  398. default:
  399. usage();
  400. }
  401. }
  402. if(optind == argc){
  403. printf("No input files provided.\n");
  404. usage();
  405. }
  406. // Process the provided files
  407. for (; optind < argc; optind++) {
  408. chA = chB = 0;
  409. // Generate output name
  410. strcpy(pngfilename, argv[optind]);
  411. strcpy(name, basename(pngfilename));
  412. strtok(name, ".");
  413. extension = strtok(NULL, ".");
  414. if (pngdirname[0] == '\0')
  415. strcpy(pngdirname, dirname(pngfilename));
  416. if(strcmp(extension, "png") == 0){
  417. printf("Reading %s", argv[optind]);
  418. if(readRawImage(argv[optind], prow, &nrow) == 0){
  419. fprintf(stderr, "Skipping %s; see above.\n", name);
  420. continue;
  421. }
  422. }else{
  423. // Open sound file, exit if that fails
  424. if (initsnd(argv[optind]) == 0) exit(1);
  425. // Main image building loop
  426. for (nrow = 0; nrow < 3000; nrow++) {
  427. // Allocate 2150 floats worth of memory for every line of the image
  428. prow[nrow] = (float *) malloc(sizeof(float) * 2150);
  429. // Read into prow and break the loop once we reach the end of the image
  430. if (getpixelrow(prow[nrow], nrow, &zenith) == 0) break;
  431. printf("Row: %d\r", nrow);
  432. fflush(stdout);
  433. }
  434. // Close sound file
  435. sf_close(inwav);
  436. }
  437. if(zenith == 0 & mapFile[0] != '\0'){
  438. fprintf(stderr, "WARNING: Guessing peak elevation in image, map will most likely not be aligned.\n");
  439. zenith = nrow / 2;
  440. }
  441. printf("\nTotal rows: %d\n", nrow);
  442. // Calibrate
  443. chA = calibrate(prow, nrow, CHA_OFFSET, CH_WIDTH);
  444. chB = calibrate(prow, nrow, CHB_OFFSET, CH_WIDTH);
  445. printf("Channel A: %s (%s)\n", ch.id[chA], ch.name[chA]);
  446. printf("Channel B: %s (%s)\n", ch.id[chB], ch.name[chB]);
  447. // Temperature
  448. if (CONTAINS(imgopt, 't') && chB >= 4) {
  449. // TODO: Doesn't work with channel 4
  450. //temperature(prow, nrow, chB, CHB_OFFSET);
  451. sprintf(pngfilename, "%s/%s-t.png", pngdirname, name);
  452. ImageOut(pngfilename, "Temperature", prow, nrow, 909, CHB_OFFSET, (char *)TempPalette, enhancements, mapFile);
  453. }
  454. // False color image
  455. if(CONTAINS(imgopt, 'c')){
  456. if (chA == 2 && chB >= 4) { // Normal false color
  457. sprintf(pngfilename, "%s/%s-c.png", pngdirname, name);
  458. //ImageRGBOut(pngfilename, prow, nrow);
  459. ImageOut(pngfilename, "False Color", prow, nrow, CH_WIDTH, 0, NULL, enhancements, mapFile);
  460. } else if (chB == 2) { // GVI (global vegetation index) false color
  461. Ngvi(prow, nrow);
  462. sprintf(pngfilename, "%s/%s-c.png", pngdirname, name);
  463. ImageOut(pngfilename, "GVI False Color", prow, nrow, CH_WIDTH, CHB_OFFSET, (char *)GviPalette, enhancements, mapFile);
  464. } else {
  465. fprintf(stderr, "Skipping False Color generation; lacking required channels.\n");
  466. }
  467. }
  468. // MCIR
  469. if (CONTAINS(imgopt, 'm')) {
  470. sprintf(pngfilename, "%s/%s-m.png", pngdirname, name);
  471. ImageOut(pngfilename, "MCIR", prow, nrow, 909, CHA_OFFSET, NULL, enhancements, mapFile);
  472. }
  473. // Histogram equalise
  474. if(CONTAINS(enhancements, 'h')){
  475. histogramEqualise(prow, nrow, CHA_OFFSET, CH_WIDTH);
  476. histogramEqualise(prow, nrow, CHB_OFFSET, CH_WIDTH);
  477. }
  478. // Raw image
  479. if (CONTAINS(imgopt, 'r')) {
  480. char channelstr[45];
  481. sprintf(channelstr, "%s (%s) & %s (%s)", ch.id[chA], ch.name[chA], ch.id[chB], ch.name[chB]);
  482. sprintf(pngfilename, "%s/%s-r.png", pngdirname, name);
  483. ImageOut(pngfilename, channelstr, prow, nrow, IMG_WIDTH, 0, NULL, enhancements, mapFile);
  484. }
  485. // Channel A
  486. if (CONTAINS(imgopt, 'a')) {
  487. char channelstr[21];
  488. sprintf(channelstr, "%s (%s)", ch.id[chA], ch.name[chA]);
  489. sprintf(pngfilename, "%s/%s-%s.png", pngdirname, name, ch.id[chA]);
  490. ImageOut(pngfilename, channelstr, prow, nrow, CH_WIDTH, CHA_OFFSET, NULL, enhancements, mapFile);
  491. }
  492. // Channel B
  493. if (CONTAINS(imgopt, 'b')) {
  494. char channelstr[21];
  495. sprintf(channelstr, "%s (%s)", ch.id[chB], ch.name[chB]);
  496. sprintf(pngfilename, "%s/%s-%s.png", pngdirname, name, ch.id[chB]);
  497. ImageOut(pngfilename, channelstr, prow, nrow, CH_WIDTH , CHB_OFFSET, NULL, enhancements, mapFile);
  498. }
  499. // Distribution image
  500. if (CONTAINS(imgopt, 'd')) {
  501. sprintf(pngfilename, "%s/%s-d.png", pngdirname, name);
  502. distrib(pngfilename, prow, nrow);
  503. }
  504. }
  505. exit(0);
  506. }