diff --git a/README.md b/README.md index 8b8f614..3f1ef07 100644 --- a/README.md +++ b/README.md @@ -256,7 +256,10 @@ the ray counts before each slab is loaded. To preserve a render for later exposure and tone-mapping work, build the desired backend with `ENABLE_HDR=1`, for example `make SPACETIME=schwarzschild ENABLE_HDR=1`. This produces `build/Release/schwarzschild_sky`, -which accepts `--hdr-output PATH`. It writes the pre-tone-mapping RGB +which accepts `--hdr-output`. This switch writes the HDR file next to the +ordinary output, replacing its extension with `_HDR.fits`; for example, +`--output output/imgs/ring.png --hdr-output` writes +`output/imgs/ring_HDR.fits`. It writes the pre-tone-mapping RGB framebuffer as a three-plane, 32-bit float FITS image. Values remain linear HDR at the renderer's arbitrary scale; no tone mapping or per-frame normalization is applied. The ordinary diff --git a/src/main.c b/src/main.c index 380a03b..16f5000 100644 --- a/src/main.c +++ b/src/main.c @@ -32,7 +32,8 @@ typedef struct { const char *all_sky_catalog_path; const char *output_path; #ifdef ENABLE_HDR_OUTPUT - const char *hdr_output_path; + int write_hdr_output; + char hdr_output_path[PATH_MAX]; #endif const char *observer_track_path; const char *frames_dir; @@ -143,6 +144,26 @@ static int validate_tonemapped_output_path(const char *path) { return -1; } +#ifdef ENABLE_HDR_OUTPUT +static int make_hdr_output_path(char path[PATH_MAX], const char *output_path) { + static const char hdr_suffix[] = "_HDR.fits"; + const char *filename = strrchr(output_path, '/'); + const char *extension; + size_t stem_length; + + filename = filename == NULL ? output_path : filename + 1; + extension = strrchr(filename, '.'); + stem_length = extension != NULL && extension != filename + ? (size_t)(extension - output_path) + : strlen(output_path); + if (stem_length + sizeof hdr_suffix > PATH_MAX) + return -1; + memcpy(path, output_path, stem_length); + memcpy(path + stem_length, hdr_suffix, sizeof hdr_suffix); + return 0; +} +#endif + static int parse_args(int argc, char **argv, Settings *s, const char **write_path) { #ifdef ENABLE_PNG @@ -190,8 +211,8 @@ static int parse_args(int argc, char **argv, Settings *s, else if (!strcmp(argv[i], "--output") && i + 1 < argc) s->output_path = argv[++i]; #ifdef ENABLE_HDR_OUTPUT - else if (!strcmp(argv[i], "--hdr-output") && i + 1 < argc) - s->hdr_output_path = argv[++i]; + else if (!strcmp(argv[i], "--hdr-output")) + s->write_hdr_output = 1; #endif else if (!strcmp(argv[i], "--width") && i + 1 < argc && !parse_int(argv[++i], &s->width)) { @@ -450,7 +471,7 @@ static int render_observer_frame(const Settings *s, StarCatalog *catalog, if (s->draw_mesh) frame_draw_mesh(&mesh, hdr, s->width, s->height, 0.5, 0.5); #ifdef ENABLE_HDR_OUTPUT - if (s->hdr_output_path != NULL && + if (s->write_hdr_output && write_hdr_fits(s->hdr_output_path, hdr, s->width, s->height, s->horizontal_fov_deg)) { fprintf(stderr, "Failed to write HDR FITS image: %s\n", s->hdr_output_path); @@ -667,7 +688,7 @@ int main(int argc, char **argv) { "[--max-magnification M] [--max-cache-psf-flux F] " "[--psf-direct] [--verbose] " #ifdef ENABLE_HDR_OUTPUT - "[--hdr-output PATH] " + "[--hdr-output] " #endif "[--coarse-cell-pixels N] [--refine-max-level N " "--refine-angle-abs-deg D --refine-angle-rel R " @@ -693,7 +714,7 @@ int main(int argc, char **argv) { ? 0 : (perror(settings.write_minkowski_accel_track_path), 1); #ifdef ENABLE_HDR_OUTPUT - if (settings.frames_dir != NULL && settings.hdr_output_path != NULL) { + if (settings.frames_dir != NULL && settings.write_hdr_output) { fputs("--hdr-output is available only for a single-frame render.\n", stderr); return 2; } @@ -701,6 +722,13 @@ int main(int argc, char **argv) { if (settings.frames_dir == NULL && validate_tonemapped_output_path(settings.output_path)) return 2; +#ifdef ENABLE_HDR_OUTPUT + if (settings.write_hdr_output && + make_hdr_output_path(settings.hdr_output_path, settings.output_path)) { + fputs("HDR output path is too long.\n", stderr); + return 2; + } +#endif StarCatalog catalog = {0}; if (settings.all_sky_catalog_path != NULL) { if (catalog_load_all_sky(&catalog, settings.all_sky_catalog_path)) {