From aee200e30423e1e7a11bd098cd6341c071c05251 Mon Sep 17 00:00:00 2001 From: Xerbo Date: Wed, 3 Jun 2020 14:55:32 +0100 Subject: [PATCH] Rewrite `ImageOut`, remove channel ID in filename --- README.md | 3 +- common.h | 21 ++++++- main.c | 44 +++++++------- pngio.c | 179 ++++++++++++++++++++++++++++++------------------------ 4 files changed, 144 insertions(+), 103 deletions(-) diff --git a/README.md b/README.md index 8d81412..0450c50 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,8 @@ Generated images are outputted in PNG and are 24 bit RGB for all image types apa Image names are `audiofile-x.png`, where `x` is: - `r` for raw images - - Sensor ID (`1`, `2`, `3A`, `3B`, `4`, `5`) for channel A|B images + - `a` channel A images + - `b` channel B images - `p` for a paletted image - `t` for temperature calibrated images - `m` for MCIR images diff --git a/common.h b/common.h index 2a4a50f..7dc01f0 100644 --- a/common.h +++ b/common.h @@ -32,6 +32,7 @@ typedef struct { typedef struct { float *prow[MAX_HEIGHT]; // Row buffers int nrow; // Number of rows + int zenith; int chA, chB; // ID of each channel char name[256]; // Stripped filename char *palette; // Filename of palette @@ -45,4 +46,22 @@ typedef struct { int realtime; // Realtime decoding char *filename; // Output filename char *palette; // Filename of palette -} options_t; \ No newline at end of file +} options_t; + +enum imagetypes { + Raw_Image='r', + MCIR='m', + Palleted='p', + Temperature='t', + Channel_A='a', + Channel_B='b', + Distribution='d' +}; +enum effects { + Crop_Telemetry='t', + Histogram_Equalise='h', + Denoise='d', + Precipitation_Overlay='p', + Flip_Image='f', + Linear_Equalise='l' +}; \ No newline at end of file diff --git a/main.c b/main.c index 4acfca4..aac4042 100644 --- a/main.c +++ b/main.c @@ -54,8 +54,6 @@ extern void flipImage(image_t *img, int width, int offset); extern char GviPalette[256*3]; extern char TempPalette[256*3]; -// Row where the satellite is closest to the observer -int zenith = 0; // Audio file static SNDFILE *audioFile; // Number of channels in audio file @@ -174,7 +172,7 @@ static int processAudio(char *filename, options_t *opts){ img.prow[img.nrow] = (float *) malloc(sizeof(float) * 2150); // Write into memory and break the loop when there are no more samples to read - if (getpixelrow(img.prow[img.nrow], img.nrow, &zenith, (img.nrow == 0)) == 0) + if (getpixelrow(img.prow[img.nrow], img.nrow, &img.zenith, (img.nrow == 0)) == 0) break; if(opts->realtime) pushRow(img.prow[img.nrow], IMG_WIDTH); @@ -193,9 +191,9 @@ static int processAudio(char *filename, options_t *opts){ // Fallback for detecting the zenith // TODO: encode metadata in raw images - if(opts->map != NULL && opts->map[0] != '\0' && zenith == 0){ + if(opts->map != NULL && opts->map[0] != '\0' && img.zenith == 0){ fprintf(stderr, "Guessing zenith in image, map will most likely be misaligned.\n"); - zenith = img.nrow / 2; + img.zenith = img.nrow / 2; } // Calibrate @@ -205,67 +203,67 @@ static int processAudio(char *filename, options_t *opts){ printf("Channel B: %s (%s)\n", ch.id[img.chB], ch.name[img.chB]); // Denoise - if(CONTAINS(opts->effects, 'd')){ + if(CONTAINS(opts->effects, Denoise)){ denoise(img.prow, img.nrow, CHA_OFFSET, CH_WIDTH); denoise(img.prow, img.nrow, CHB_OFFSET, CH_WIDTH); } // Flip, for southbound passes - if(CONTAINS(opts->effects, 'f')){ + if(CONTAINS(opts->effects, Flip_Image)){ flipImage(&img, CH_WIDTH, CHA_OFFSET); flipImage(&img, CH_WIDTH, CHB_OFFSET); } // Temperature - if (CONTAINS(opts->type, 't') && img.chB >= 4) { + if (CONTAINS(opts->type, Temperature) && img.chB >= 4) { temperature(opts, &img, CHB_OFFSET, CH_WIDTH); - ImageOut(opts, &img, CHB_OFFSET, CH_WIDTH, "Temperature", "t", (char *)TempPalette); + ImageOut(opts, &img, CHB_OFFSET, CH_WIDTH, "Temperature", Temperature, (char *)TempPalette); } // MCIR - if (CONTAINS(opts->type, 'm')) - ImageOut(opts, &img, CHA_OFFSET, CH_WIDTH, "MCIR", "m", NULL); + if (CONTAINS(opts->type, MCIR)) + ImageOut(opts, &img, CHA_OFFSET, CH_WIDTH, "MCIR", MCIR, NULL); // Linear equalise - if(CONTAINS(opts->effects, 'l')){ + if(CONTAINS(opts->effects, Linear_Equalise)){ linearEnhance(img.prow, img.nrow, CHA_OFFSET, CH_WIDTH); linearEnhance(img.prow, img.nrow, CHB_OFFSET, CH_WIDTH); } // Histogram equalise - if(CONTAINS(opts->effects, 'h')){ + if(CONTAINS(opts->effects, Histogram_Equalise)){ histogramEqualise(img.prow, img.nrow, CHA_OFFSET, CH_WIDTH); histogramEqualise(img.prow, img.nrow, CHB_OFFSET, CH_WIDTH); } // Raw image - if (CONTAINS(opts->type, 'r')) { + if (CONTAINS(opts->type, Raw_Image)) { sprintf(desc, "%s (%s) & %s (%s)", ch.id[img.chA], ch.name[img.chA], ch.id[img.chB], ch.name[img.chB]); - ImageOut(opts, &img, 0, IMG_WIDTH, desc, "r", NULL); + ImageOut(opts, &img, 0, IMG_WIDTH, desc, Raw_Image, NULL); } // Palette image - if (CONTAINS(opts->type, 'p')) { + if (CONTAINS(opts->type, Palleted)) { img.palette = opts->palette; strcpy(desc, "Palette composite"); - ImageOut(opts, &img, 0, 909, desc, "p", NULL); + ImageOut(opts, &img, CHA_OFFSET, 909, desc, Palleted, NULL); } // Channel A - if (CONTAINS(opts->type, 'a')) { + if (CONTAINS(opts->type, Channel_A)) { sprintf(desc, "%s (%s)", ch.id[img.chA], ch.name[img.chA]); - ImageOut(opts, &img, CHA_OFFSET, CH_WIDTH, desc, ch.id[img.chA], NULL); + ImageOut(opts, &img, CHA_OFFSET, CH_WIDTH, desc, Channel_A, NULL); } // Channel B - if (CONTAINS(opts->type, 'b')) { + if (CONTAINS(opts->type, Channel_B)) { sprintf(desc, "%s (%s)", ch.id[img.chB], ch.name[img.chB]); - ImageOut(opts, &img, CHB_OFFSET, CH_WIDTH, desc, ch.id[img.chB], NULL); + ImageOut(opts, &img, CHB_OFFSET, CH_WIDTH, desc, Channel_B, NULL); } // Value distribution image - if (CONTAINS(opts->type, 'd')) - distrib(opts, &img, "d"); + if (CONTAINS(opts->type, Distribution)) + distrib(opts, &img, Distribution); return 1; } diff --git a/pngio.c b/pngio.c index 3f4b5c7..747c800 100644 --- a/pngio.c +++ b/pngio.c @@ -26,7 +26,6 @@ #include "common.h" #include "offsets.h" -extern int zenith; extern char PrecipPalette[256*3]; extern rgb_t applyPalette(char *palette, int val); extern rgb_t RGBcomposite(rgb_t top, float top_a, rgb_t bottom, float bottom_a); @@ -193,7 +192,7 @@ int readRawImage(char *filename, float **prow, int *nrow) { return 1; } -int readPalette(char *filename, rgb_t **crow) { +int readPalette(char *filename, rgb_t **pixels) { FILE *fp = fopen(filename, "r"); if(!fp) { fprintf(stderr, "Cannot open %s\n", filename); @@ -222,7 +221,7 @@ int readPalette(char *filename, rgb_t **crow) { fprintf(stderr, "Palette must be 8 bit color.\n"); return 0; }else if(color_type != PNG_COLOR_TYPE_RGBA){ - fprintf(stderr, "Palette must be RGB.\n"); + fprintf(stderr, "Palette must be RGBA.\n"); return 0; } @@ -241,10 +240,10 @@ int readPalette(char *filename, rgb_t **crow) { // Put into crow for(int y = 0; y < height; y++) { - crow[y] = (rgb_t *) malloc(sizeof(rgb_t) * width); + pixels[y] = (rgb_t *) malloc(sizeof(rgb_t) * width); for(int x = 0; x < width; x++) - crow[y][x] = (rgb_t){ + pixels[y][x] = (rgb_t){ PNGrows[y][x*4], PNGrows[y][x*4 + 1], PNGrows[y][x*4 + 2] @@ -254,35 +253,92 @@ int readPalette(char *filename, rgb_t **crow) { return 1; } -png_text meta[] = { - {PNG_TEXT_COMPRESSION_NONE, "Software", VERSION}, - {PNG_TEXT_COMPRESSION_NONE, "Channel", "Unknown", 7}, - {PNG_TEXT_COMPRESSION_NONE, "Description", "NOAA satellite image", 20} -}; +void prow2crow(float **prow, int nrow, char palette, rgb_t **crow){ + for(int y = 0; y < nrow; y++){ + crow[y] = (rgb_t *) malloc(sizeof(rgb_t) * IMG_WIDTH); -int ImageOut(options_t *opts, image_t *img, int offset, int width, char *desc, char *chid, char *palette){ - char outName[384]; + for(int x = 0; x < IMG_WIDTH; x++){ + if(palette == NULL) + crow[y][x].r = crow[y][x].g = crow[y][x].b = prow[y][x]; + else + crow[y][x] = applyPalette(palette, prow[y][x]); + } + } +} + +int applyUserPalette(float **prow, int nrow, char *filename, rgb_t **crow){ + rgb_t *pal_row[256]; + if(!readPalette(filename, pal_row)){ + fprintf(stderr, "Could not read palette"); + return 0; + } + + for(int y = 0; y < nrow; y++){ + for(int x = 0; x < CH_WIDTH; x++){ + int cha = prow[y][x + CHA_OFFSET]; + int cbb = prow[y][x + CHB_OFFSET]; + crow[y][x + CHA_OFFSET] = pal_row[cbb][cha]; + } + } + + return 1; +} + +int ImageOut(options_t *opts, image_t *img, int offset, int width, char *desc, char chid, char *palette){ + char outName[512]; if(opts->filename == NULL || opts->filename[0] == '\0'){ - sprintf(outName, "%s/%s-%s.png", opts->path, img->name, chid); + sprintf(outName, "%s/%s-%c.png", opts->path, img->name, chid); }else{ - sprintf(outName, "%s/%s", opts->path, opts -> filename); + sprintf(outName, "%s/%s", opts->path, opts->filename); } - meta[1].text = desc; - meta[1].text_length = sizeof(desc); - - FILE *pngfile; + png_text meta[] = { + {PNG_TEXT_COMPRESSION_NONE, "Software", VERSION}, + {PNG_TEXT_COMPRESSION_NONE, "Channel", desc, sizeof(desc)}, + {PNG_TEXT_COMPRESSION_NONE, "Description", "NOAA satellite image", 20} + }; + + // Parse image type + int greyscale = 1; + switch (chid){ + case Palleted: + greyscale = 0; + break; + case Temperature: + greyscale = 0; + break; + case MCIR: + greyscale = 0; + break; + case Raw_Image: break; + case Channel_A: break; + case Channel_B: break; + } - // Reduce the width of the image to componsate for the missing telemetry - int fc = (chid[0] == 'c'); - int greyscale = 0; - int skiptele = 0; - int imgpalette = (chid[0] == 'p'); - if(opts->effects != NULL && CONTAINS(opts->effects, 't')){ - width -= TOTAL_TELE; - skiptele = 1; + // Parse effects + int crop_telemetry = 0; + for(int i = 0; i < strlen(opts->effects); i++){ + switch (opts->effects[i]) { + case Crop_Telemetry: + width -= TOTAL_TELE; + offset += SYNC_WIDTH + SPC_WIDTH; + crop_telemetry = 1; + break; + case Precipitation_Overlay: + greyscale = 0; + break; + case Flip_Image: break; + case Denoise: break; + case Histogram_Equalise: break; + case Linear_Equalise: break; + default: + fprintf(stderr, "NOTICE: Unrecognised effect, \"%c\"\n", opts->effects[i]); + break; + } } + FILE *pngfile; + // Create writer png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png_ptr) { @@ -297,9 +353,7 @@ int ImageOut(options_t *opts, image_t *img, int offset, int width, char *desc, c return 0; } - if(palette == NULL && !CONTAINS(opts->effects, 'p') && !fc && opts->map[0] == '\0' && chid[0] != 'm' && !imgpalette){ - greyscale = 1; - + if(greyscale){ // Greyscale image png_set_IHDR(png_ptr, info_ptr, width, img->nrow, 8, PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE, @@ -323,52 +377,39 @@ int ImageOut(options_t *opts, image_t *img, int offset, int width, char *desc, c png_init_io(png_ptr, pngfile); png_write_info(png_ptr, info_ptr); - // Move prow into crow, crow ~ color rows + // Move prow into crow, crow ~ color rows, if required rgb_t *crow[img->nrow]; - if(!greyscale && !fc){ - for(int y = 0; y < img->nrow; y++){ - crow[y] = (rgb_t *) malloc(sizeof(rgb_t) * IMG_WIDTH); + if(!greyscale) + prow2crow(img->prow, img->nrow, palette, crow); - for(int x = 0; x < IMG_WIDTH; x++){ - if(palette == NULL) - crow[y][x].r = crow[y][x].g = crow[y][x].b = img->prow[y][x]; - else - crow[y][x] = applyPalette(palette, img->prow[y][x]); - } - } - } + // Apply a user provided color palette + if(CONTAINS(opts->type, Palleted)) + applyUserPalette(img->prow, img->nrow, opts->palette, crow); // Precipitation - // TODO: use temperature calibration - if(CONTAINS(opts->effects, 'p')){ + // TODO: use temperature calibration, and a better palette + if(CONTAINS(opts->effects, Precipitation_Overlay)){ for(int y = 0; y < img->nrow; y++){ for(int x = 0; x < CH_WIDTH; x++){ if(img->prow[y][x + CHB_OFFSET] > 191) - crow[y][x + CHB_OFFSET] = applyPalette(PrecipPalette, img->prow[y][x + CHB_OFFSET]); + crow[y][x + CHB_OFFSET] = crow[y][x + CHA_OFFSET] = applyPalette(PrecipPalette, img->prow[y][x + CHB_OFFSET]); } } } // Map stuff if(opts->map != NULL && opts->map[0] != '\0'){ - if(mapOverlay(opts->map, crow, img->nrow, zenith, (chid[0] == 'm')) == 0){ + if(!mapOverlay(opts->map, crow, img->nrow, img->zenith, CONTAINS(opts->type, MCIR))){ fprintf(stderr, "Skipping MCIR generation.\n"); return 0; } - }else if(chid[0] == 'm'){ + }else if(CONTAINS(opts->type, MCIR)){ fprintf(stderr, "Skipping MCIR generation; no map provided.\n"); return 0; } printf("Writing %s", outName); - rgb_t *pal_row[256]; - if(imgpalette){ - if(!readPalette(img->palette, pal_row)){ - return 0; - } - } - // Build image for (int y = 0; y < img->nrow; y++) { png_color pix[width]; // Color @@ -376,33 +417,12 @@ int ImageOut(options_t *opts, image_t *img, int offset, int width, char *desc, c int skip = 0; for (int x = 0; x < width; x++) { - if(skiptele){ - switch (x) { - case 0: - skip += SYNC_WIDTH + SPC_WIDTH; - break; - case CH_WIDTH: - skip += TELE_WIDTH + SYNC_WIDTH + SPC_WIDTH; - break; - } + if(crop_telemetry && x == CH_WIDTH){ + skip += TELE_WIDTH + SYNC_WIDTH + SPC_WIDTH; } if(greyscale){ mpix[x] = img->prow[y][x + skip + offset]; - }else if(fc){ - pix[x] = (png_color){ - CLIP(img->prow[y][x + CHA_OFFSET], 0, 255), - CLIP(img->prow[y][x + CHA_OFFSET], 0, 255), - CLIP(img->prow[y][x + CHB_OFFSET], 0, 255) - }; - }else if(imgpalette){ - int cha = img->prow[y][x + CHA_OFFSET]; - int cbb = img->prow[y][x + CHB_OFFSET]; - pix[x] = (png_color){ - pal_row[cbb][cha].r, - pal_row[cbb][cha].g, - pal_row[cbb][cha].b - }; }else{ pix[x] = (png_color){ crow[y][x + skip + offset].r, @@ -437,8 +457,11 @@ int initWriter(options_t *opts, image_t *img, int width, int height, char *desc, char outName[384]; sprintf(outName, "%s/%s-%s.png", opts->path, img->name, chid); - meta[1].text = desc; - meta[1].text_length = sizeof(desc); + png_text meta[] = { + {PNG_TEXT_COMPRESSION_NONE, "Software", VERSION}, + {PNG_TEXT_COMPRESSION_NONE, "Channel", desc, sizeof(desc)}, + {PNG_TEXT_COMPRESSION_NONE, "Description", "NOAA satellite image", 20} + }; // Create writer rt_png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);