Previously, pfm images were unconditionally assumed to be little-endian images. However, pfm images also come in a big-endian flavor, so this patch rewrites dt_imageio_open_pfm to support arbitrary endianness.
It has been minimally-tested by ensuring that both flavors of pfm images open properly and look correct. No normalization is performed, so some pfms can be hard to control with the base curve and tone curve modules, which seem to assume that all values lie in the [0,1] interval.
From ded24c8d4ecd7a4ecdfcd6197dd65ddb7c25722c Mon Sep 17 00:00:00 2001 From: Taahir Ahmed <ahmed.taa...@gmail.com> Date: Sat, 16 May 2015 03:15:40 -0500 Subject: [PATCH] Add support for big-endian pfm images. Previously, pfm images were unconditionally assumed to be little-endian images. However, pfm images also come in a big-endian flavor, so this patch rewrites dt_imageio_open_pfm to support arbitrary endianness. It has been minimally-tested by ensuring that both flavors of pfm images open properly and look correct. No normalization is performed, so some pfms can be hard to control with the base curve and tone curve modules, which seem to assume that all values lie in the [0,1] interval. --- src/common/imageio_pfm.c | 276 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 227 insertions(+), 49 deletions(-) diff --git a/src/common/imageio_pfm.c b/src/common/imageio_pfm.c index 6101dd9..f18bb4d 100644 --- a/src/common/imageio_pfm.c +++ b/src/common/imageio_pfm.c @@ -21,75 +21,253 @@ #include "common/darktable.h" #include "common/imageio_pfm.h" -#include <stdio.h> +#include <assert.h> +#include <ctype.h> +#include <fcntl.h> #include <stdlib.h> #include <strings.h> #include <math.h> #include <assert.h> #include <time.h> +#include <sys/mman.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> +#define PARSEUL_OK 0 +#define PARSEUL_ENOINPUT 1 +#define PARSEUL_ERANGE 2 + +static int parse_ul( + const unsigned char *src, + const unsigned char *lim, + unsigned long *result, + const unsigned char * *const end +) +{ + unsigned long accum = 0; + *end = src; + while(*end != lim && isdigit(**end)) + { + /* The standard guarantees that 0-9 are contiguous. */ + unsigned long new_accum = accum * 10 + (**end - '0'); + + /* Unsigned arithmetic is modular, so this is safe. */ + if(new_accum < accum) + return PARSEUL_ERANGE; + + accum = new_accum; + + ++*end; + } + + if(*end == src) + return PARSEUL_ENOINPUT; + + *result = accum; + return PARSEUL_OK; +} + +#define PFM_BE 1 +#define PFM_LE 2 + dt_imageio_retval_t dt_imageio_open_pfm(dt_image_t *img, const char *filename, dt_mipmap_buffer_t *mbuf) { - const char *ext = filename + strlen(filename); - while(*ext != '.' && ext > filename) ext--; - if(strcasecmp(ext, ".pfm")) return DT_IMAGEIO_FILE_CORRUPTED; - FILE *f = fopen(filename, "rb"); - if(!f) return DT_IMAGEIO_FILE_CORRUPTED; - int ret = 0; - int cols = 3; - char head[2] = { 'X', 'X' }; - ret = fscanf(f, "%c%c\n", head, head + 1); - if(ret != 2 || head[0] != 'P') goto error_corrupt; - if(head[1] == 'F') - cols = 3; - else if(head[1] == 'f') - cols = 1; + _Static_assert( + (sizeof(uint32_t) == sizeof(float)) + && (sizeof(uint32_t) == 4), + "pfm support requires IEEE 754 b32 floats." + ); + + dt_imageio_retval_t retval = DT_IMAGEIO_OK; + + int fd; + if((fd = open(filename, O_RDONLY)) == -1) + { + retval = DT_IMAGEIO_FILE_NOT_FOUND; + goto cleanup_exit_error; + } + + /* Lookup file size. */ + struct stat sb; + if(fstat(fd, &sb) == -1) + { + retval = DT_IMAGEIO_FILE_NOT_FOUND; + goto cleanup_close_file; + } + + /* Map the file. */ + void *buf = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if(buf == MAP_FAILED) + { + retval = DT_IMAGEIO_FILE_NOT_FOUND; + goto cleanup_close_file; + } + + const unsigned char *cur = (const unsigned char*) buf; + const unsigned char *lim = (const unsigned char*) (buf + sb.st_size); + + /* Take the magic sequence. It is either "PF" or "Pf". The first means this + * is a three-channel file (presumably RGB), and the second means that it + * is a single-channel file (presumable greyscale). */ + + if(lim == cur || *cur != 'P') + { + retval = DT_IMAGEIO_FILE_CORRUPTED; + goto cleanup_unmap_file; + } + + ++cur; + + size_t channels; + if(lim != cur && *cur == 'f') + channels = 1; + else if(lim != cur && *cur == 'F') + channels = 3; else - goto error_corrupt; - ret = fscanf(f, "%d %d\n%*[^\n]", &img->width, &img->height); - if(ret != 2) goto error_corrupt; - ret = fread(&ret, sizeof(char), 1, f); - if(ret != 1) goto error_corrupt; - ret = 0; + { + retval = DT_IMAGEIO_FILE_CORRUPTED; + goto cleanup_unmap_file; + } + + ++cur; - float *buf = (float *)dt_mipmap_cache_alloc(mbuf, img); - if(!buf) goto error_cache_full; + /* Take a delimiter. The "spec" says there should be one, but we can parse + * unambigously without any. */ + while(cur != lim && isspace(*cur)) + ++cur; + if(cur == lim) + { + retval = DT_IMAGEIO_FILE_CORRUPTED; + goto cleanup_unmap_file; + } + + /* Take the image dimensions : positive integer, delimiter, positive integer, + * delimiter */ + unsigned long read_width = 0; + unsigned long read_height = 0; - if(cols == 3) + if(parse_ul(cur, lim, &read_width, &cur) != PARSEUL_OK + || read_width >= INT32_MAX) + { + retval = DT_IMAGEIO_FILE_CORRUPTED; + goto cleanup_unmap_file; + } + while(cur != lim && isspace(*cur)) + ++cur; + if(cur == lim) + { + retval = DT_IMAGEIO_FILE_CORRUPTED; + goto cleanup_unmap_file; + } + if(parse_ul(cur, lim, &read_height, &cur) != PARSEUL_OK + || read_height >= INT32_MAX) + { + retval = DT_IMAGEIO_FILE_CORRUPTED; + goto cleanup_unmap_file; + } + while(cur != lim && isspace(*cur)) + ++cur; + if(cur == lim) { - ret = fread(buf, 3 * sizeof(float), (size_t)img->width * img->height, f); - for(size_t i = (size_t)img->width * img->height; i > 0; i--) - for(int c = 0; c < 3; c++) buf[4 * (i - 1) + c] = fmaxf(0.0f, fminf(FLT_MAX, buf[3 * (i - 1) + c])); + retval = DT_IMAGEIO_FILE_CORRUPTED; + goto cleanup_unmap_file; } + + img->width = read_width; + img->height = read_height; + + /* Take the scale factor. We only care about whether it's positive or + * negative. Negative scale factors indicate little-endian storage. Positive + * scale factors indicate big-endian storage.*/ + + int order; + if(*cur == '-') + order = PFM_LE; else - for(size_t j = 0; j < img->height; j++) - for(size_t i = 0; i < img->width; i++) + order = PFM_BE; + ++cur; + + /* Skip over the rest of the scale factor, and a single whitespace + * character. */ + while(cur != lim && !isspace(*cur)) + ++cur; + if(cur == lim || !isspace(*cur)) + { + retval = DT_IMAGEIO_FILE_CORRUPTED; + goto cleanup_unmap_file; + } + ++cur; + + /* Now cur points to the beginning of the packed binary storage. */ + + /* Check that we have the correct number of floats. */ + if((lim - cur) != (read_width * read_height * channels * sizeof(float))) + { + retval = DT_IMAGEIO_FILE_CORRUPTED; + goto cleanup_unmap_file; + } + + float *dstbuf = (float *) dt_mipmap_cache_alloc(mbuf, img); + if(NULL == buf) + { + retval = DT_IMAGEIO_CACHE_FULL; + goto cleanup_unmap_file; + } + + for(size_t r_u = 0; r_u < read_height; ++r_u) + { + size_t r = read_height - r_u - 1; + for(size_t c = 0; c < read_width; ++c) + { + /* Buffer is 4 channels, densely-packed, row-major. */ + size_t idx = (r * read_width + c) * 4; + + for(size_t chan = 0; chan < channels; ++chan) { - ret = fread(buf + 4 * (img->width * j + i), sizeof(float), 1, f); - buf[4 * (img->width * j + i) + 2] = buf[4 * (img->width * j + i) + 1] - = buf[4 * (img->width * j + i) + 0]; + /* Depending on which C or C++ standard you look at, using a union in + * this way can variously be undefined behavior, implementation-defined + * behavior, or perfectly ok. However, both gcc and clang Do The Right + * Thing here. */ + union { float flt; uint32_t bit; } a; + + if(order == PFM_BE) + a.bit + = ((uint32_t)(cur[0]) << 24) + | ((uint32_t)(cur[1]) << 16) + | ((uint32_t)(cur[2]) << 8) + | ((uint32_t)(cur[3]) << 0); + else + a.bit + = ((uint32_t)(cur[0]) << 0) + | ((uint32_t)(cur[1]) << 8) + | ((uint32_t)(cur[2]) << 16) + | ((uint32_t)(cur[3]) << 24); + + cur += 4; + + float val = a.flt; + val = fmaxf(0.0f, val); + val = fminf(FLT_MAX, val); + + dstbuf[idx + chan] = val; } - float *line = (float *)calloc(4 * img->width, sizeof(float)); - for(size_t j = 0; j < img->height / 2; j++) - { - memcpy(line, buf + img->width * j * 4, 4 * sizeof(float) * img->width); - memcpy(buf + img->width * j * 4, buf + img->width * (img->height - 1 - j) * 4, - 4 * sizeof(float) * img->width); - memcpy(buf + img->width * (img->height - 1 - j) * 4, line, 4 * sizeof(float) * img->width); + + /* Stretch greyscale input over the first three channels. */ + if(channels == 1) + dstbuf[idx + 2] = dstbuf[idx + 1] = dstbuf[idx + 0]; + + /* Set channel 4. */ + dstbuf[idx + 3] = 0.0f; + } } - free(line); - fclose(f); - return DT_IMAGEIO_OK; - -error_corrupt: - fclose(f); - return DT_IMAGEIO_FILE_CORRUPTED; -error_cache_full: - fclose(f); - return DT_IMAGEIO_CACHE_FULL; + + cleanup_unmap_file: + munmap(buf, sb.st_size); + cleanup_close_file: + close(fd); + cleanup_exit_error: + return retval; } // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh -- 2.0.5
signature.asc
Description: This is a digitally signed message part.
------------------------------------------------------------------------------ One dashboard for servers and applications across Physical-Virtual-Cloud Widest out-of-the-box monitoring support with 50+ applications Performance metrics, stats and reports that give you Actionable Insights Deep dive visibility with transaction tracing using APM Insight. http://ad.doubleclick.net/ddm/clk/290420510;117567292;y
_______________________________________________ darktable-devel mailing list darktable-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/darktable-devel