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:
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;