commit bb81f0f60505bcf7902532537545ae56a6d72dbb
parent 674593823e865a74874a4b541c0b8931d712262a
Author: Vincent Forest <vincent.forest@meso-star.com>
Date: Thu, 7 May 2020 16:51:56 +0200
Merge branch 'release_0.3'
Diffstat:
5 files changed, 618 insertions(+), 210 deletions(-)
diff --git a/README.md b/README.md
@@ -29,6 +29,21 @@ informations on CMake.
## Release notes
+### Version 0.3
+
+- Add the `-i` option that regroups all the options controlling the
+ post-processing of the pixel color. The options `-e` and `-w` that defined
+ the exposure and the white scale factor are thus removed.
+- Add support of raw data visualisation through color palettes. The `-m` option
+ enables the mapping of a pixel component to a color ramp. Both, the pixel
+ component to visualise and the palette to use can be set through this option.
+ The data range to visualise can also be fixed. When this option is defined,
+ the `-v` option can be used to displays into the terminal the color ramp and
+ its associated values.
+- Remove the `-u` and `-T` options that are previously used to roughly
+ visualise the per pixel uncertainties and the estimate of the per realisation
+ time: the new `-m` option proposes a far more efficient alternative.
+
### Version 0.2.1
- Update how the image exposure is handled: the pixels are multiplied by
@@ -40,7 +55,7 @@ informations on CMake.
- Fix the XYZ to sRGB conversion: the reference white was not correctly set.
- Fix the gamma correction of the linear sRGB color: there was an issue in the
-used formulae.
+ used formulae.
### Version 0.1
@@ -50,10 +65,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
@@ -1,4 +1,5 @@
-# Copyright (C) 2018-2019 CNRS, |Meso|Star>, Université Paul Sabatier
+# Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
+# Copyright (C) 2018, 2019 Université Paul Sabatier
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -22,7 +23,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})
@@ -35,8 +37,8 @@ include_directories(${RSys_INCLUDE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
# Configure and define targets
################################################################################
set(VERSION_MAJOR 0)
-set(VERSION_MINOR 2)
-set(VERSION_PATCH 1)
+set(VERSION_MINOR 3)
+set(VERSION_PATCH 0)
set(VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH})
configure_file(${HTPP_SOURCE_DIR}/htpp_version.h.in
@@ -50,7 +52,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/doc/htpp.1.txt b/doc/htpp.1.txt
@@ -1,4 +1,5 @@
-// Copyright (C) 2018-2019 CNRS, |Meso|Star>, Université Paul Sabatier
+// Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
+// Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -28,12 +29,18 @@ htpp [_option_] ... [_input_]
DESCRIPTION
-----------
-*htpp* post-processes an *htrdr-image*(5) and convert it to a regular
-PPM image [1]. If _input_ is not defined, the *htrdr-image*(5) is read from
-standard input.
-*htpp* tone maps the high dynamic range colors of the _input_ image with the
-following filmic tone mapping operator [2]:
+*htpp* post-processes a *htrdr-image*(5) and converts it to a regular PPM
+image [1]. If _input_ is not defined, the *htrdr-image*(5) is read from
+standard input. Two post-processing procedures are provided: the
+post-processing of the image colors (option *-i*), and the mapping of a given
+pixel component onto a color ramp (option "*-m*). By default, *htpp*
+post-processes the image color.
+
+To post-process the image colors (option *-i*) *htpp* assumes that the first,
+the third and the fifth components of each *htrdr-image*(5) pixel encode a
+color with respect to the CIE 1931 XYZ color space. *htpp* first tone maps
+these colors with the following filmic tone mapping operator [2]:
out-color = f(in-color * exposure) / f(white-scale)
@@ -48,47 +55,78 @@ with:
E = 0.02
F = 0.30
-The _exposure_ term is an user defined parameter provided by the *-e* option.
-If not defined, a default _exposure_ of 1 is used. The _white-scale_ factor is
-either defined by the user through the *-w* option or automatically computed
-as the luminance from which roughly all image pixels have a luminance less
-than _white-scale_. Currently, *htpp* empirically defines _white-scale_ as the
-luminance greater than the luminance of 99.5% of the pixels. Once tone mapped,
-the pixels are transformed from the CIE 1931 XYZ color space to the sRGB
-linear color space before to be gamma corrected. Finally, the resulting pixel
-components are clamped to [0, 1] and encoded on 8-bits.
+The _exposure_ term is an user-defined parameter provided as the _exposure_
+image option. If not defined, a default _exposure_ of 1 is used. The
+_white-scale_ factor is either defined by the user through the _white_ image
+option or automatically computed as the luminance that is such that 99.5% of
+pixel radiances are less than _white-scale_. Once tone mapped, the pixels are
+transformed from the CIE 1931 XYZ color space into the sRGB linear color space
+before being gamma corrected. Finally, the resulting pixel components are
+clamped to [0, 1] and encoded on 8-bits.
+
+The mapping of a pixel component onto a color ramp is controlled by the *-m*
+option. The pixel component to post-process is defined by the _pixcpnt_
+mapping option. *htpp* normalizes this component according to its range onto
+the whole image or with respect to the _range_ mapping option if it is
+defined. The resuling value is then mapped to a built-in color ramp whose name
+is defined by the _palette_ mapping option.
OPTIONS
-------
-*-e* _exposure_::
- Pixel exposure. By default its value is 1.
-
*-f*::
Force overwrite of the _output_ file.
*-h*::
List short help and exit.
+*-i* <__sub-option__>[:<__sub-option__> ...]::
+ Post-process the color of the *htrdr-image*(5). The first, the third and the
+ fifth pixel components are assumed to store the pixel color encoded into the
+ CIE 1931 XYZ color space. Available sub-options are:
+
+ **default**;;
+ Use the default values of the sub-options.
+
+ **exposure**=__real__;;
+ Pixel exposure. By default its value is 1.
+
+ **white**=__white-scale__;;
+ Factor used to normalize input colors. If not defined, the white scale is
+ automatically computed from the luminance of the _input_ image.
+
+*-m* <__sub-option__>[:<__sub-option__> ...]::
+ Map a pixel component to a regular color. Available sub options are:
+
+ **default**;;
+ Use the default values of the sub-options.
+
+ **palette**=__palette-name__;;
+ Color palette to use. Available palettes are the ones supported
+ by the Star-ColorMap library [3]. The default palette is inferno.
+
+ **pixcpnt**=__pixel-component__;;
+ Index in [0, 7] of the pixel component to map. The default pixel component
+ is the first one, i.e. *pixcpnt*=0.
+
+ **range**=__min__,__max__;;
+ Range of the values to map. A degenerated range (i.e. __min__ >= __max__)
+ means that this range is automatically computed from the boundaries of the
+ selected pixel component over the whole image. This is the default
+ comportment.
+
*-o* _output_::
File where the PPM image is written. If not defined, write _output_ to
standard output.
-*-u*::
- Generate an image of the uncertainties of the pixels rather of their
- estimated radiance. The uncertainties are tone mapped as if they were
- regular colors but no XYZ to sRGB conversion is applied on the tone mapped
- values.
-
-*-T*::
- Generate an image of the per radiative path computation time rather than an
- image of the estimated radiance.
-
*-t* _threads-count_::
Hint on the number of threads to use. By default use as many threads as CPU
cores.
*-v*::
- Make *htpp* verbose.
+ Make *htpp* verbose. When used in post-processing of the pixel color (*-i*
+ option), this option displays the __white-scale__ factor used to normalize
+ the colors. In pixel component mapping mode (*-m* option), this option
+ displays the color ramp and its associated values.
*-w _white-scale_*::
Factor used to normalize input colors. If not defined, _white-scale_ is
@@ -110,24 +148,26 @@ Convert *img.htrdr* and visualise the resulting image by redirecting the
standard output to the *feh*(1) image viewer. Use an _exposure_ of *0.5* and
explicitly define the normalization factor to *0.0025*:
- $ htpp -e 0.5 -w 0.0025 img.htrdr | feh -
+ $ htpp -i exposure=0.5:white=0.0025 img.htrdr | feh -
-Use the *-u* option to visualise the uncertainty of the *img.htrdr* image
-rather than its estimated radiance:
+Use the *-m* option to map the values of the second pixel component clamped in
+[0, 2] to the color ramp _magma_.
- $ htpp -u img.htrdr | feh -
+ $ htpp -m pixcpnt=1:palette=magma:range=0 img.htrdr | feh -
NOTES
-----
1. Portable PixMap - <http://netpbm.sourceforge.net/doc/ppm.html>
2. Filmic tone mapping operator -
<http://filmicworlds.com/blog/filmic-tonemapping-operators/>
+3. Star-ColorMap - <https://gitlab.com/meso-star/star-cmap>
COPYRIGHT
---------
-Copyright © 2018-2019 CNRS, |Meso|Star> <contact@meso-star.com>,
-Université Paul Sabatier <contact-edstar@laplace.univ-tlse.fr>. *htpp* is free
-software released under the GPLv3+ license: GNU GPL version 3 or later
+Copyright © 2018, 2019, 2020 |Meso|Star> <contact@meso-star.com>.
+Copyright © 2018, 2019 CNRS, Université Paul Sabatier
+<contact-edstar@laplace.univ-tlse.fr>. *htpp* is free software released under
+the GPLv3+ license: GNU GPL version 3 or later
<https://gnu.org/licenses/gpl.html>. You are free to change and redistribute
it. There is NO WARRANTY, to the extent permitted by law.
diff --git a/src/htpp.c b/src/htpp.c
@@ -1,4 +1,5 @@
-/* Copyright (C) 2018-2019 CNRS, |Meso|Star>, Université Paul Sabatier
+/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,8 +21,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 +33,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 +100,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 +114,209 @@ 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("\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 <sub-option>[:<sub-option> ... ]\n"
+" post process the colors of the submitted image. The\n"
+" first, third, and fifth pixel component are assumed\n"
+" to store a color encoded in the CIE 1931 XYZ color\n"
+" space ('man htpp' for the list of sub-options).\n");
printf(
-" -u dump per channel uncertainties rather than radiance.\n");
+" -m <sub-option>[:<sub-option> ... ]\n"
+" map a specific pixel component to a color.\n"
+" ('man htpp' for the list of sub-options).\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;
+ } 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 +345,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 +478,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 +494,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 +512,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 +551,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;
+ }
+
+ 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(cstr_to_list_uint(b, ' ', definition, NULL, 2) != RES_OK) {
- fprintf(stderr, "%s: invalid image definition '%s'\n",
- stream_name, b) ;
+ 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 +663,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 +684,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 +698,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 +716,167 @@ compute_img_normalization_factor(const struct img* img)
return Ymax;
}
+static uint8_t
+rgb_to_c256(const uint8_t rgb[3])
+{
+ uint8_t c256 = 0;
+ ASSERT(rgb);
+
+ if(rgb[0] == rgb[1] && rgb[1] == rgb[2]) {
+ int c = rgb[0];
+ if(c < 4) {
+ c256 = 16; /* Grey 0 */
+ } else if(c >= 243) {
+ c256 = 231; /* Grey 100 */
+ } else {
+ c256 = (uint8_t)(232 + (c-8+5) / 10);
+ }
+ } else {
+ int r, g, b;
+ r = rgb[0] < 48 ? 0 : (rgb[0] < 95 ? 1 : 1 + (rgb[0] - 95 + 20) / 40);
+ g = rgb[1] < 48 ? 0 : (rgb[1] < 95 ? 1 : 1 + (rgb[1] - 95 + 20) / 40);
+ b = rgb[2] < 48 ? 0 : (rgb[2] < 95 ? 1 : 1 + (rgb[2] - 95 + 20) / 40);
+ c256 = (uint8_t)(36*r + 6*g + b + 16);
+ }
+ return c256;
+}
+
+static void
+print_color_map(const struct scmap* scmap, const double range[2])
+{
+ const double ransz = range[1] - range[0];
+ const int map_length = 65;
+ const int map_quarter = map_length / 4;
+ const int label_length = map_length / 4;
+ int i;
+ ASSERT(range && range[0] < range[1]);
+
+ FOR_EACH(i, 0, map_length) {
+ const double u = (double)i / (double)(map_length-1);
+ double color[3] = {0,0,0};
+ uint8_t rgb[3];
+ uint8_t c256;
+ SCMAP(fetch_color(scmap, u, SCMAP_FILTER_LINEAR, color));
+
+ rgb[0] = (uint8_t)(CLAMP(color[0], 0, 1) * 255. + 0.5/*round*/);
+ rgb[1] = (uint8_t)(CLAMP(color[1], 0, 1) * 255. + 0.5/*round*/);
+ rgb[2] = (uint8_t)(CLAMP(color[2], 0, 1) * 255. + 0.5/*round*/);
+ c256 = rgb_to_c256(rgb);
+ if(i == 0 * map_quarter
+ || i == 1 * map_quarter
+ || i == 2 * map_quarter
+ || i == 3 * map_quarter
+ || i == 4 * map_quarter) {
+ fprintf(stderr, "\x1b[0m|");
+ } else {
+ fprintf(stderr, "\x1b[48;5;%dm ", c256);
+ }
+ }
+ fprintf(stderr, "\n");
+ fprintf(stderr, "%-*.5g", label_length, range[0]);
+ fprintf(stderr, "%-*.5g", label_length, 0.25 * ransz + range[0]);
+ fprintf(stderr, "%-*.5g", label_length, 0.50 * ransz + range[0]);
+ fprintf(stderr, "%-*.5g", label_length, 0.75 * ransz + range[0]);
+ fprintf(stderr, "%-*.5g", label_length, range[1]);
+ fprintf(stderr, "\n");
+}
+
+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;
+ }
+
+ if(args->map.range[0] < args->map.range[1]) {
+ /* The range is fixed */
+ range[0] = args->map.range[0];
+ range[1] = args->map.range[1];
+ } else {
+ /* The range is defined from the loaded data */
+ range[0] = img->ranges[args->map.pixcpnt][0];
+ range[1] = img->ranges[args->map.pixcpnt][1];
+ }
+ ransz = range[1] - range[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__;
+ if(ransz == 0) {
+ pixel[PIXCPNT_R] = 0;
+ pixel[PIXCPNT_G] = 0;
+ pixel[PIXCPNT_B] = 0;
+ } else {
+ 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];
+ }
+ }
+
+ if(args->verbose) {
+ print_color_map(scmap, range);
+ }
+
+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 +889,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 +910,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 +929,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;
diff --git a/src/htpp_version.h.in b/src/htpp_version.h.in
@@ -1,4 +1,5 @@
-/* Copyright (C) 2018 CNRS, |Meso|Star>, Université Paul Sabatier
+/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by