htpp

htrdr-image post-processing
git clone git://git.meso-star.fr/htpp.git
Log | Files | Refs | README | LICENSE

commit 727f26820edf6bcfd668fb5150e12c2223c3e0b7
parent 99c23c41f8d08c95f4a9b842017200c0bbf29865
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Tue, 24 Mar 2020 16:17:15 +0100

Add support of color map

Diffstat:
MREADME.md | 12++++++------
Mcmake/CMakeLists.txt | 5+++--
Msrc/htpp.c | 589+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
3 files changed, 438 insertions(+), 168 deletions(-)

diff --git a/README.md b/README.md @@ -50,10 +50,10 @@ used formulae. ## License -htpp copyright (C) 2018-2019 Centre National de la Recherche -(CNRS), [|Meso|Star>](https://www.meso-star.com) <contact@meso-star.com>, -Université Paul Sabatier <contact-edstar@laplace.univ-tlse.fr>. It is free -software released under the GPL v3+ license: GNU GPL version 3 or later. You -are welcome to redistribute it under certain conditions; refer to the COPYING -file for details. +Copyright (C) 2018, 2019, 2029 [|Meso|Star>](https://www.meso-star.com) +<contact@meso-star.com>. Copyright (C) 2018-2019 Centre National de la +Recherche (CNRS), Université Paul Sabatier +<contact-edstar@laplace.univ-tlse.fr>. htpp is free software released under the +GPL v3+ license: GNU GPL version 3 or later. You are welcome to redistribute it +under certain conditions; refer to the COPYING file for details. diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt @@ -22,7 +22,8 @@ set(HTPP_SOURCE_DIR ${PROJECT_SOURCE_DIR}/../src) # Check dependencies ################################################################################ find_package(RCMake 0.3 REQUIRED) -find_package(RSys 0.6 REQUIRED) +find_package(RSys 0.9 REQUIRED) +find_package(StarCMap 0.0 REQUIRED) find_package(OpenMP 1.2 REQUIRED) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${RCMAKE_SOURCE_DIR}) @@ -50,7 +51,7 @@ rcmake_prepend_path(HTPP_FILES_SRC ${HTPP_SOURCE_DIR}) rcmake_prepend_path(HTPP_FILES_DOC ${PROJECT_SOURCE_DIR}/../) add_executable(htpp ${HTPP_FILES_SRC}) -target_link_libraries(htpp RSys) +target_link_libraries(htpp RSys StarCMap) set_target_properties(htpp PROPERTIES COMPILE_FLAGS "${OpenMP_C_FLAGS}") diff --git a/src/htpp.c b/src/htpp.c @@ -20,8 +20,10 @@ #include <rsys/cstr.h> #include <rsys/double33.h> #include <rsys/mem_allocator.h> -#include <rsys/stretchy_array.h> #include <rsys/rsys.h> +#include <rsys/text_reader.h> + +#include <star/scmap.h> #include <errno.h> #include <fcntl.h> /* open */ @@ -30,24 +32,65 @@ #include <sys/stat.h> /* S_IRUSR & S_IWUSR */ #include <unistd.h> /* getopt */ -enum pixel_data { - PIXEL_RADIANCE, - PIXEL_UNCERTAINTY, - PIXEL_TIME +enum pixcpnt { + PIXCPNT_X, + PIXCPNT_R = PIXCPNT_X, + PIXCPNT_X_STDERR, + PIXCPNT_Y, + PIXCPNT_G = PIXCPNT_Y, + PIXCPNT_Y_STDERR, + PIXCPNT_Z, + PIXCPNT_B = PIXCPNT_Z, + PIXCPNT_Z_STDERR, + PIXCPNT_TIME, + PIXCPNT_TIME_STDERR, + PIXCPNTS_COUNT__ +}; + +enum pp_type { + PP_IMAGE, + PP_MAP }; struct args { const char* input; const char* output; - double exposure; - double white_scale; - enum pixel_data pixdata; + + enum pp_type pp_type; + + struct image_opt { + double exposure; /* In [0, inf) */ + double white; /* In ]0, inf) */ + } image; + + struct map_opt { + const struct scmap_palette* palette; + unsigned pixcpnt; /* In [0, PIXCPNTS_COUNT__[ */ + double range[2]; + } map; + int verbose; int force_overwrite; int nthreads; int quit; }; -#define ARGS_DEFAULT__ {NULL,NULL,1.0,-1.0,PIXEL_RADIANCE,0,0,INT_MAX,0} +#define ARGS_DEFAULT__ { \ + NULL, /* Input */ \ + NULL, /* Output */ \ + PP_IMAGE, /* Post process type */ \ + { \ + 1.0, /* Image exposure */ \ + -1.0, /* Image white scale */ \ + }, { \ + &scmap_palette_inferno, /* Map palette */ \ + 0, /* Map channel */ \ + {-DBL_MAX,DBL_MAX}, /* Range */ \ + }, \ + 0, /* Verbosity level */ \ + 0, /* Force overwrite? */ \ + INT_MAX, /* #threads */ \ + 0 /* Quit? */ \ +} static const struct args ARGS_DEFAULT = ARGS_DEFAULT__; struct img { @@ -56,10 +99,10 @@ struct img { size_t height; size_t pitch; /* #bytes of a row */ - double Yrange[2]; /* Luminance range */ + /* Ranges of the loaded value */ + double ranges[PIXCPNTS_COUNT__][2]; }; -#define IMG_NULL__ {NULL,0,0,0,{0,0}} -static const struct img IMG_NULL = IMG_NULL__; +static const struct img IMG_NULL; /******************************************************************************* * Helper functions @@ -70,38 +113,211 @@ print_help(const char* cmd) ASSERT(cmd); printf( -"Usage: %s [OPTIONS] [INPUT]\n" -"Tone map a htrdr-image(5) and convert it in a regular PPM image.\n\n", +"Usage: %s [options] [image]\n" +"Post process a htrdr-image(5) and convert the result in a regular PPM\n" +"image. If no image name is defined, read the image data from\n" +"standard input\n", cmd); printf( -" -e EXPOSURE exposure of the pixel. Default value is 1.\n"); - printf( " -f overwrite the OUTPUT file if it already exists.\n"); printf( " -h display this help and exit.\n"); printf( -" -o OUTPUT write PPM image to OUTPUT. If not defined, write results\n" -" to standard output.\n"); +" -i <image-option>[:<image-option> ... ]\n" +" handle the input as an image whose first, third,\n" +" and fourth pixel component store the pixel color in\n" +" the CIE 1931 XYZ color space\n"); printf( -" -u dump per channel uncertainties rather than radiance.\n"); +" -m <map-option>[:<map-option> ... ]\n" +" map a specific pixel component to a color.\n"); printf( -" -T dump per realiastion time rather than radiance.\n"); +" -o <output> write PPM image to <output>. If not defined, write\n" +" results to standard output.\n"); printf( -" -t THREADS hint on the number of threads to use. By default use as\n" -" many threads as CPU cores.\n"); +" -t <threads-count>\n" +" hint on the number of threads to use.\n" +" By default use as many threads as CPU cores.\n"); printf( " -v make the program verbose.\n"); printf( -" -w WHITE-SCALE Factor used to normalize input colors. By default, it is\n" -" automatically computed from the luminance of the INPUT image.\n"); - printf( " --version display version information and exit.\n"); printf("\n"); printf( -"htpp (C) 2018-2019 CNRS, |Meso|Star> <contact@meso-star.com>, Université Paul\n" -"Sabatier <contact-edstar@laplace.univ-tlse.fr>. This is free software released\n" -"under the GNU GPL license, version 3 or later. You are free to change or\n" -"redistribute it under certain conditions <http://gnu.org/licenses/gpl.html>.\n"); +"Copyright (C) 2018, 2019, 2020 |Meso|Star> <contact@meso-star.com>.\n" +"Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier\n" +"<contact-edstar@laplace.univ-tlse.fr>. htpp is free software released\n" +"under the GNU GPL license, version 3 or later. You are free to change\n" +"or redistribute it under certain conditions\n" +"<http://gnu.org/licenses/gpl.html>.\n"); +} + +static res_T +parse_multiple_options + (struct args* args, + const char* str, + res_T (*parse_option)(struct args* args, const char* str)) +{ + char buf[512]; + char* tk; + char* ctx; + res_T res = RES_OK; + ASSERT(args && str); + + if(strlen(str) >= sizeof(buf) - 1/*NULL char*/) { + fprintf(stderr, "Could not duplicate the option string `%s'.\n", str); + res = RES_MEM_ERR; + goto error; + } + strncpy(buf, str, sizeof(buf)); + + tk = strtok_r(buf, ":", &ctx); + do { + res = parse_option(args, tk); + if(res != RES_OK) goto error; + tk = strtok_r(NULL, ":", &ctx); + } while(tk); + +exit: + return res; +error: + goto exit; +} + +static res_T +parse_img_option(struct args* args, const char* str) +{ + char buf[128]; + char* key; + char* val; + char* tk_ctx; + res_T res = RES_OK; + + if(strlen(str) >= sizeof(buf) - 1/*NULL char*/) { + fprintf(stderr, + "Could not duplicate the image options string `%s'.\n", str); + res = RES_MEM_ERR; + goto error; + } + strncpy(buf, str, sizeof(buf)); + + key = strtok_r(buf, "=", &tk_ctx); + val = strtok_r(NULL, "", &tk_ctx); + + if(!strcmp(key, "default")) { + if(val) { + fprintf(stderr, "Unexpected value to the image option `%s'.\n", key); + res = RES_BAD_ARG; + goto error; + } + args->image = ARGS_DEFAULT.image; + + } else { + + if(!val) { + fprintf(stderr, "Missing value to the image option `%s'.\n", key); + res = RES_BAD_ARG; + goto error; + } + + if(!strcmp(key, "exposure")) { + res = cstr_to_double(val, &args->image.exposure); + if(res != RES_OK) goto error; + if(args->image.exposure < 0) { + fprintf(stderr, "Invalid image exposure %g.\n", args->image.exposure); + res = RES_BAD_ARG; + goto error; + } + } else if(!strcmp(key, "white")) { + res = cstr_to_double(val, &args->image.white); + if(res != RES_OK) goto error; + if(args->image.exposure < 0) { + fprintf(stderr, "Invalid image white scale %g.\n", args->image.white); + res = RES_BAD_ARG; + goto error; + } + } else { + fprintf(stderr, "Invalid image option `%s'.\n", key); + res = RES_BAD_ARG; + goto error; + } + } + +exit: + return res; +error: + goto exit; +} + +static res_T +parse_map_option(struct args* args, const char* str) +{ + char buf[128]; + char* key; + char* val; + char* tk_ctx; + res_T res = RES_OK; + + if(strlen(str) >= sizeof(buf) - 1/*NULL char*/) { + fprintf(stderr, + "Could not duplicate the map options string `%s'.\n", str); + res = RES_MEM_ERR; + goto error; + } + strncpy(buf, str, sizeof(buf)); + + key = strtok_r(buf, "=", &tk_ctx); + val = strtok_r(NULL, "", &tk_ctx); + + if(!strcmp(key, "default")) { + if(val) { + fprintf(stderr, "Unexpected value to the map option `%s'.\n", key); + res = RES_BAD_ARG; + goto error; + } + args->map = ARGS_DEFAULT.map; + + } else { + if(!val) { + fprintf(stderr, "Missing value to the map option `%s'.\n", key); + res = RES_BAD_ARG; + goto error; + } + + if(!strcmp(key, "pixcpnt")) { + res = cstr_to_uint(val, &args->map.pixcpnt); + if(res != RES_OK) goto error; + if(args->map.pixcpnt >= PIXCPNTS_COUNT__) { + fprintf(stderr, "Invalid pixel component `%u'.\n", args->map.pixcpnt); + res = RES_BAD_ARG; + goto error; + } + } else if(!strcmp(key, "palette")) { + args->map.palette = scmap_get_builtin_palette(val); + if(!args->map.palette) { + fprintf(stderr, "Invalid palette `%s'.\n", val); + res = RES_BAD_ARG; + goto error; + } + } else if(!strcmp(key, "range")) { + size_t len; + res = cstr_to_list_double(val, ',', args->map.range, &len, 2); + if(res != RES_OK) goto error; + if(len != 2 || args->map.range[0] >= args->map.range[1]) { + fprintf(stderr, "Invalid map range `%s'.\n", val); + res = RES_BAD_ARG; + goto error; + } + } else { + fprintf(stderr, "Invalid map option `%s'.\n", key); + res = RES_BAD_ARG; + goto error; + } + } + +exit: + return res; +error: + goto exit; } static void @@ -130,30 +346,29 @@ args_init(struct args* args, const int argc, char** argv) } } - while((opt = getopt(argc, argv, "e:fho:Tt:uvw:")) != -1) { + /* Begin the optstring by ':' to make silent getopt */ + while((opt = getopt(argc, argv, "fhi:m:o:t:v")) != -1) { switch(opt) { - case 'e': - res = cstr_to_double(optarg, &args->exposure); - if(res == RES_OK && args->exposure < 0) res = RES_BAD_ARG; - break; case 'f': args->force_overwrite = 1; break; case 'h': print_help(argv[0]); args_release(args); args->quit = 1; goto exit; + case 'i': + args->pp_type = PP_IMAGE; + res = parse_multiple_options(args, optarg, parse_img_option); + break; + case 'm': + args->pp_type = PP_MAP; + res = parse_multiple_options(args, optarg, parse_map_option); + break; case 'o': args->output = optarg; break; - case 'T': args->pixdata = PIXEL_TIME; break; case 't': res = cstr_to_int(optarg, &args->nthreads); if(res == RES_OK && args->nthreads <= 0) res = RES_BAD_ARG; break; - case 'u': args->pixdata = PIXEL_UNCERTAINTY; break; case 'v': args->verbose = 1; break; - case 'w': - res = cstr_to_double(optarg, &args->white_scale); - if(res == RES_OK && args->white_scale <= 0) res = RES_BAD_ARG; - break; default: res = RES_BAD_ARG; break; } if(res != RES_OK) { @@ -264,7 +479,10 @@ error: /* http://filmicworlds.com/blog/filmic-tonemapping-operators/ */ static double* -filmic_tone_mapping(double pixel[3], const double exposure, const double Ymax) +filmic_tone_mapping + (double pixel[PIXCPNTS_COUNT__], + const double exposure, + const double Ymax) { const double A = 0.15; const double B = 0.50; @@ -277,15 +495,15 @@ filmic_tone_mapping(double pixel[3], const double exposure, const double Ymax) const double white_scale = TONE_MAP(W); ASSERT(pixel); - pixel[0] = TONE_MAP(pixel[0]*exposure) / white_scale; - pixel[1] = TONE_MAP(pixel[1]*exposure) / white_scale; - pixel[2] = TONE_MAP(pixel[2]*exposure) / white_scale; + pixel[PIXCPNT_X] = TONE_MAP(pixel[PIXCPNT_X]*exposure) / white_scale; + pixel[PIXCPNT_Y] = TONE_MAP(pixel[PIXCPNT_Y]*exposure) / white_scale; + pixel[PIXCPNT_Z] = TONE_MAP(pixel[PIXCPNT_Z]*exposure) / white_scale; #undef TONE_MAP return pixel; } static double* -XYZ_to_sRGB(double XYZ[3]) +XYZ_to_sRGB(double pixel[PIXCPNTS_COUNT__]) { #define D65_x 0.31271 #define D65_y 0.32902 @@ -295,51 +513,33 @@ XYZ_to_sRGB(double XYZ[3]) -1.5371385, 1.8760108, -0.2040259, -0.4985314, 0.0415560, 1.0572252 }; - double* sRGB = XYZ; - double* XYZ_D65 = XYZ; - ASSERT(XYZ); + double XYZ[3], sRGB[3], XYZ_D65[3]; + ASSERT(pixel); + + XYZ[0] = pixel[PIXCPNT_X]; + XYZ[1] = pixel[PIXCPNT_Y]; + XYZ[2] = pixel[PIXCPNT_Z]; + d3_mul(XYZ_D65, XYZ, D65); d33_muld3(sRGB, mat, XYZ_D65); sRGB[0] = MMAX(sRGB[0], 0); sRGB[1] = MMAX(sRGB[1], 0); sRGB[2] = MMAX(sRGB[2], 0); - return sRGB; -} -static double* -sRGB_gamma_correct(double sRGB[3]) -{ - int i; - FOR_EACH(i, 0, 3) { - if(sRGB[i] <= 0.0031308) { - sRGB[i] = sRGB[i] * 12.92; - } else { - sRGB[i] = 1.055 * pow(sRGB[i], 1.0/2.4) - 0.055; - } - } - return sRGB; + pixel[PIXCPNT_R] = sRGB[0]; + pixel[PIXCPNT_G] = sRGB[1]; + pixel[PIXCPNT_B] = sRGB[2]; + return pixel; } -static FINLINE char* -read_line(char** buf, FILE* stream) +static double +sRGB_gamma_correct(double value) { - char* b = *buf; - const int chunk = 128; - ASSERT(buf); - if(!b) b = sa_add(b, 128); - - do { - if(!fgets(b, (int)sa_size(b), stream)) return NULL; - - /* Ensure that he whole line is read */ - while(!strrchr(b, '\n') && !feof(stream)) { - CHK(fgets(sa_add(b, (size_t)chunk)-1/*'\0' char*/, chunk+1, stream)); - } - - b[strcspn(b, "#\n\r")] = '\0'; /* Rm new line & comments */ - } while(strspn(b, " \t") == strlen(b)); /* Empty line */ - *buf = b; - return b; + if(value <= 0.0031308) { + return value * 12.92; + } else { + return 1.055 * pow(value, 1.0/2.4) - 0.055; + } } static void @@ -352,89 +552,93 @@ img_release(struct img* img) static res_T img_load (struct img* img, - const enum pixel_data pixdata, FILE* stream, const char* stream_name) { - char* b = NULL; /* Temporary buffer used to read the stream */ + struct txtrdr* txtrdr = NULL; + size_t icpnt; size_t x, y; - unsigned definition[2]; + unsigned resolution[2]; res_T res = RES_OK; ASSERT(img && stream); - memset(img, 0, sizeof(*img)); + *img = IMG_NULL; - if(!read_line(&b, stream)) { - fprintf(stderr, "%s: could not read the image definition.\n", - stream_name); - res = RES_IO_ERR; + res = txtrdr_stream(NULL, stream, stream_name, '#', &txtrdr); + if(res != RES_OK) { + fprintf(stderr, "%s: could not setup the text reader -- %s.\n", + txtrdr_get_name(txtrdr), res_to_cstr(res)); goto error; } - if(cstr_to_list_uint(b, ' ', definition, NULL, 2) != RES_OK) { - fprintf(stderr, "%s: invalid image definition '%s'\n", - stream_name, b) ; + res = txtrdr_read_line(txtrdr); + if(res != RES_OK) { + fprintf(stderr, "%s: could not read the image resolution -- %s.\n", + txtrdr_get_name(txtrdr), res_to_cstr(res)); + goto error; + } + + if(!txtrdr_get_line(txtrdr)) { + fprintf(stderr, "%s: missing image definition.\n", txtrdr_get_name(txtrdr)); res = RES_BAD_ARG; goto error; } - img->width = (size_t)definition[0]; - img->height = (size_t)definition[1]; - img->pitch = ALIGN_SIZE(sizeof(double[3])*img->width, 16u); + res = cstr_to_list_uint(txtrdr_get_cline(txtrdr), ' ', resolution, NULL, 2); + if(res != RES_OK) { + fprintf(stderr, "%s: invalid image resolution `%s' -- %s\n", + txtrdr_get_name(txtrdr), txtrdr_get_cline(txtrdr), res_to_cstr(res)) ; + goto error; + } - img->pixels = mem_alloc(img->pitch * img->height); + img->width = (size_t)resolution[0]; + img->height = (size_t)resolution[1]; + img->pitch = ALIGN_SIZE(sizeof(double[PIXCPNTS_COUNT__])*img->width, 16u); + img->pixels = mem_calloc(img->height, img->pitch); if(!img->pixels) { fprintf(stderr, "Could not allocate the image pixels.\n"); res = RES_MEM_ERR; goto error; } - /* Read pixels and compute the luminance range of the image */ - img->Yrange[0] = DBL_MAX; - img->Yrange[1] =-DBL_MAX; + /* Reset the range of each pixel components */ + FOR_EACH(icpnt, 0, PIXCPNTS_COUNT__) { + img->ranges[icpnt][0] = DBL_MAX; + img->ranges[icpnt][1] = -DBL_MAX; + } + + /* Read pixel components */ FOR_EACH(y, 0, img->height) { double* row = (double*)(img->pixels + y*img->pitch); FOR_EACH(x, 0, img->width) { - double tmp[8] = {0}; - double* pixel = row + x*3; - if(!read_line(&b, stream)) { + double* pixel = row + x*PIXCPNTS_COUNT__; + size_t ncpnts; + + res = txtrdr_read_line(txtrdr); + if(res != RES_OK) { fprintf(stderr, - "%s: could not read the XYZ value of the (%lu, %lu) pixel.\n", - stream_name, (unsigned long)x, (unsigned long)y); - res = RES_IO_ERR; + "%s: could not read the components of the pixel (%lu, %lu) -- %s.\n", + txtrdr_get_name(txtrdr), (unsigned long)x, (unsigned long)y, + res_to_cstr(res)); goto error; } - if(cstr_to_list_double(b, ' ', tmp, 0, 8) != RES_OK /* X, Y, Z, Time */ - && cstr_to_list_double(b, ' ', tmp, 0, 6) != RES_OK /* X, Y, Z */) { - fprintf(stderr, "%s: invalid XYZ[Time] value for the (%lu, %lu) pixel.\n", - stream_name, (unsigned long)x, (unsigned long)y); + + res = cstr_to_list_double(txtrdr_get_cline(txtrdr), ' ', pixel, &ncpnts, 8); + if(res != RES_OK || ncpnts < 6) { + fprintf(stderr, "%s: invalid components for the (%lu, %lu) pixel.\n", + txtrdr_get_name(txtrdr), (unsigned long)x, (unsigned long)y); res = RES_BAD_ARG; goto error; } - switch(pixdata) { - case PIXEL_RADIANCE: - pixel[0] = tmp[0]; - pixel[1] = tmp[2]; - pixel[2] = tmp[4]; - break; - case PIXEL_UNCERTAINTY: - pixel[0] = tmp[1]; - pixel[1] = tmp[3]; - pixel[2] = tmp[5]; - break; - case PIXEL_TIME: - pixel[0] = tmp[6]; - pixel[1] = tmp[6]; - pixel[2] = tmp[6]; - break; - default: FATAL("Unreachable code.\n"); break; + + FOR_EACH(icpnt, 0, ncpnts) { + img->ranges[icpnt][0] = MMIN(img->ranges[icpnt][0], pixel[icpnt]); + img->ranges[icpnt][1] = MMAX(img->ranges[icpnt][1], pixel[icpnt]); } - img->Yrange[0] = MMIN(img->Yrange[0], pixel[1]); - img->Yrange[1] = MMAX(img->Yrange[1], pixel[1]); } } exit: - if(b) sa_release(b); + if(txtrdr) txtrdr_ref_put(txtrdr); return res; error: img_release(img); @@ -460,11 +664,11 @@ img_write_ppm(const struct img* img, FILE* stream, const char* stream_name) FOR_EACH(y, 0, img->height) { const double* row = (double*)(img->pixels + y*img->pitch); FOR_EACH(x, 0, img->width) { - const double* pixel = row + x*3; + const double* pixel = row + x*PIXCPNTS_COUNT__; i = fprintf(stream, "%i %i %i\n", - (uint8_t)(CLAMP(pixel[0], 0.0, 1.0) * 255.0 + 0.5/*Round*/), - (uint8_t)(CLAMP(pixel[1], 0.0, 1.0) * 255.0 + 0.5/*Round*/), - (uint8_t)(CLAMP(pixel[2], 0.0, 1.0) * 255.0 + 0.5/*Round*/)); + (uint8_t)(CLAMP(pixel[PIXCPNT_X], 0.0, 1.0) * 255.0 + 0.5/*Round*/), + (uint8_t)(CLAMP(pixel[PIXCPNT_Y], 0.0, 1.0) * 255.0 + 0.5/*Round*/), + (uint8_t)(CLAMP(pixel[PIXCPNT_Z], 0.0, 1.0) * 255.0 + 0.5/*Round*/)); if(i < 0) { fprintf(stderr, "%s: could not write the (%lu, %lu) pixel.\n", stream_name, (unsigned long)x, (unsigned long)y); @@ -481,7 +685,7 @@ error: } static double -compute_img_normalization_factor(const struct img* img) +compute_XYZ_normalization_factor(const struct img* img) { double* Y = NULL; double Ymax; @@ -495,8 +699,8 @@ compute_img_normalization_factor(const struct img* img) FOR_EACH(y, 0, img->height) { const double* row = (double*)(img->pixels + y*img->pitch); FOR_EACH(x, 0, img->width) { - const double* pixel = row + x*3; - Y[i] = pixel[1]; + const double* pixel = row + x*PIXCPNTS_COUNT__; + Y[i] = pixel[PIXCPNT_Y]; i++; } } @@ -513,6 +717,85 @@ compute_img_normalization_factor(const struct img* img) return Ymax; } +static res_T +pp_map(struct img* img, const struct args* args) +{ + struct scmap* scmap = NULL; + int64_t i; + double range[2]; + double ransz; + res_T res = RES_OK; + ASSERT(img && args && args->pp_type == PP_MAP); + + res = scmap_create(NULL, NULL, 1, args->map.palette, &scmap); + if(res != RES_OK) { + fprintf(stderr, "Could not create the color map -- %s.\n", + res_to_cstr(res)); + goto error; + } + range[0] = MMAX(img->ranges[args->map.pixcpnt][0], args->map.range[0]); + range[1] = MMIN(img->ranges[args->map.pixcpnt][1], args->map.range[1]); + ransz = range[1] - range[0]; + ASSERT(ransz > 0); + + omp_set_num_threads(args->nthreads); + #pragma omp parallel for + for(i=0; i < (int64_t)(img->width*img->height); ++i) { + const size_t y = (size_t)i / img->width; + const size_t x = (size_t)i % img->width; + double* row = (double*)(img->pixels + img->pitch*y); + double* pixel = row + x*PIXCPNTS_COUNT__; + const double val = CLAMP((pixel[args->map.pixcpnt] - range[0])/ransz, 0, 1); + double color[3] = {0,0,0}; + + SCMAP(fetch_color(scmap, val, SCMAP_FILTER_LINEAR, color)); + pixel[PIXCPNT_R] = color[0]; + pixel[PIXCPNT_G] = color[1]; + pixel[PIXCPNT_B] = color[2]; + } + +exit: + if(scmap) SCMAP(ref_put(scmap)); + return res; +error: + goto exit; +} + +static res_T +pp_image(struct img* img, const struct args* args) +{ + int64_t i; + double Ymax; + res_T res = RES_OK; + ASSERT(img && args && args->pp_type == PP_IMAGE); + + if(args->image.white> 0) { + Ymax = args->image.white; + } else { + Ymax = compute_XYZ_normalization_factor(img); + if(args->verbose) { + fprintf(stderr, "White scale = %g\n", Ymax); + } + } + + omp_set_num_threads(args->nthreads); + #pragma omp parallel for + for(i=0; i < (int64_t)(img->width*img->height); ++i) { + const size_t y = (size_t)i / img->width; + const size_t x = (size_t)i % img->width; + double* row = (double*)(img->pixels + img->pitch*y); + double* pixel = row + x*PIXCPNTS_COUNT__; + + filmic_tone_mapping(pixel, args->image.exposure, Ymax); + XYZ_to_sRGB(pixel); + pixel[PIXCPNT_R] = sRGB_gamma_correct(pixel[PIXCPNT_R]); + pixel[PIXCPNT_G] = sRGB_gamma_correct(pixel[PIXCPNT_G]); + pixel[PIXCPNT_B] = sRGB_gamma_correct(pixel[PIXCPNT_B]); + } + + return res; +} + /******************************************************************************* * Program ******************************************************************************/ @@ -525,10 +808,8 @@ main(int argc, char** argv) const char* stream_in_name = "stdin"; struct img img = IMG_NULL; struct args args = ARGS_DEFAULT; - double Ymax; int img_is_loaded = 0; int err = 0; - int64_t i; res_T res = RES_OK; res = args_init(&args, argc, argv); @@ -548,32 +829,16 @@ main(int argc, char** argv) fprintf(stderr, "Read image from standard input.\n"); } - res = img_load(&img, args.pixdata, stream_in, stream_in_name); + res = img_load(&img, stream_in, stream_in_name); if(res != RES_OK) goto error; img_is_loaded = 1; - if(args.white_scale > 0) { - Ymax = args.white_scale; - } else { - Ymax = compute_img_normalization_factor(&img); - if(args.verbose) fprintf(stderr, "White scale = %g\n", Ymax); - } - - omp_set_num_threads(args.nthreads); - /* Convert input HDR XYZ image in LDR image */ - #pragma omp parallel for - for(i=0; i < (int64_t)(img.width*img.height); ++i) { - const size_t y = (size_t)i / img.width; - const size_t x = (size_t)i % img.width; - double* row = (double*)(img.pixels + img.pitch*y); - double* pixel = row + x*3; - - filmic_tone_mapping(pixel, args.exposure, Ymax); /* Tone map the XYZ pixel */ - if(args.pixdata == PIXEL_RADIANCE) { - XYZ_to_sRGB(pixel); /* Convert in RGB color space */ - sRGB_gamma_correct(pixel); /* Gamma correction */ - } + switch(args.pp_type) { + case PP_IMAGE: res = pp_image(&img, &args); break; + case PP_MAP: res = pp_map(&img, &args); break; + default: FATAL("Unreachable code.\n"); break; } + if(res != RES_OK) goto error; res = img_write_ppm(&img, stream_out, stream_out_name); if(res != RES_OK) goto error; @@ -583,6 +848,10 @@ exit: if(stream_in && stream_in != stdin) fclose(stream_in); if(img_is_loaded) img_release(&img); args_release(&args); + if(mem_allocated_size() != 0) { + fprintf(stderr, "Memory leaks: %lu Bytes.\n", + (unsigned long)mem_allocated_size()); + } return err; error: err = -1;