From dfd413a5f54bd450850b5e84886949bcdf85b1e7 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jos=C3=A9=20Fonseca?= Date: Wed, 11 Sep 2013 18:41:00 +0100 Subject: [PATCH] image: Add floating point support. WIP. Not throughly tested. --- image/image.hpp | 28 ++++++-- image/image_bmp.cpp | 1 + image/image_png.cpp | 2 + image/image_pnm.cpp | 153 +++++++++++++++++++++++++++++------------ image/image_raw.cpp | 4 +- scripts/retracediff.py | 29 ++++++-- 6 files changed, 161 insertions(+), 56 deletions(-) diff --git a/image/image.hpp b/image/image.hpp index cfdf875..e36bdd0 100644 --- a/image/image.hpp +++ b/image/image.hpp @@ -37,11 +37,19 @@ namespace image { +enum ChannelType { + TYPE_UNORM8 = 0, + TYPE_FLOAT +}; + + class Image { public: unsigned width; unsigned height; unsigned channels; + ChannelType channelType; + unsigned bytesPerPixel; // Flipped vertically or not bool flipped; @@ -49,10 +57,12 @@ public: // Pixels in RGBA format unsigned char *pixels; - inline Image(unsigned w, unsigned h, unsigned c = 4, bool f = false) : + inline Image(unsigned w, unsigned h, unsigned c = 4, bool f = false, ChannelType t = TYPE_UNORM8) : width(w), height(h), channels(c), + channelType(t), + bytesPerPixel(channels * (t == TYPE_FLOAT ? 4 : 1)), flipped(f), pixels(new unsigned char[h*w*c]) {} @@ -61,24 +71,30 @@ public: delete [] pixels; } + // Absolute stride + inline unsigned + _stride() const { + return width*bytesPerPixel; + } + inline unsigned char *start(void) { - return flipped ? pixels + (height - 1)*width*channels : pixels; + return flipped ? pixels + (height - 1)*_stride() : pixels; } inline const unsigned char *start(void) const { - return flipped ? pixels + (height - 1)*width*channels : pixels; + return flipped ? pixels + (height - 1)*_stride() : pixels; } inline unsigned char *end(void) { - return flipped ? pixels - width*channels : pixels + height*width*channels; + return flipped ? pixels - _stride() : pixels + height*_stride(); } inline const unsigned char *end(void) const { - return flipped ? pixels - width*channels : pixels + height*width*channels; + return flipped ? pixels - _stride() : pixels + height*_stride(); } inline signed stride(void) const { - return flipped ? -(signed)(width*channels) : width*channels; + return flipped ? -(signed)_stride() : _stride(); } bool diff --git a/image/image_bmp.cpp b/image/image_bmp.cpp index 1961f9d..d349be0 100644 --- a/image/image_bmp.cpp +++ b/image/image_bmp.cpp @@ -71,6 +71,7 @@ struct Pixel { bool Image::writeBMP(const char *filename) const { assert(channels == 4); + assert(channelType == TYPE_UNORM8); struct FileHeader bmfh; struct InfoHeader bmih; diff --git a/image/image_png.cpp b/image/image_png.cpp index 324cfe9..826a071 100644 --- a/image/image_png.cpp +++ b/image/image_png.cpp @@ -53,6 +53,8 @@ pngWriteCallback(png_structp png_ptr, png_bytep data, png_size_t length) bool Image::writePNG(std::ostream &os) const { + assert(channelType == TYPE_UNORM8); + png_structp png_ptr; png_infop info_ptr; int color_type; diff --git a/image/image_pnm.cpp b/image/image_pnm.cpp index 9e9d0e1..a0638dc 100644 --- a/image/image_pnm.cpp +++ b/image/image_pnm.cpp @@ -40,73 +40,138 @@ namespace image { /** * http://en.wikipedia.org/wiki/Netpbm_format * http://netpbm.sourceforge.net/doc/ppm.html + * http://netpbm.sourceforge.net/doc/pfm.html */ void Image::writePNM(std::ostream &os, const char *comment) const { + const char *identifier; + unsigned outChannels; + + switch (channelType) { + case TYPE_UNORM8: + if (channels == 1) { + identifier = "P5"; + outChannels = 1; + } else { + identifier = "P6"; + outChannels = 3; + } + break; + case TYPE_FLOAT: + if (channels == 1) { + identifier = "Pf"; + outChannels = 1; + } else { + identifier = "PF"; + outChannels = 3; + } + break; + } + + os << identifier << "\n"; - os << (channels == 1 ? "P5" : "P6") << "\n"; if (comment) { os << "#" << comment << "\n"; } os << width << " " << height << "\n"; - os << "255" << "\n"; + + if (channelType == TYPE_UNORM8) { + os << "255" << "\n"; + } const unsigned char *row; - if (channels == 1 || channels == 3) { + if (channels == outChannels) { + /* + * Write whole pixel spans straight from the image buffer. + */ + for (row = start(); row != end(); row += stride()) { - os.write((const char *)row, width*channels); + os.write((const char *)row, width*bytesPerPixel); } } else { - unsigned char *tmp = new unsigned char[width*3]; - if (channels == 4) { - for (row = start(); row != end(); row += stride()) { - const uint32_t *src = (const uint32_t *)row; - uint32_t *dst = (uint32_t *)tmp; - unsigned x; - for (x = 0; x + 4 <= width; x += 4) { - /* - * It's much faster to access dwords than bytes. - * - * FIXME: Big-endian version. - */ - - uint32_t rgba0 = *src++ & 0xffffff; - uint32_t rgba1 = *src++ & 0xffffff; - uint32_t rgba2 = *src++ & 0xffffff; - uint32_t rgba3 = *src++ & 0xffffff; - uint32_t rgb0 = rgba0 - | (rgba1 << 24); - uint32_t rgb1 = (rgba1 >> 8) - | (rgba2 << 16); - uint32_t rgb2 = (rgba2 >> 16) - | (rgba3 << 8); - *dst++ = rgb0; - *dst++ = rgb1; - *dst++ = rgb2; + /* + * Need to add/remove channels, one pixel at a time. + */ + + unsigned char *tmp = new unsigned char[width*bytesPerPixel]; + + if (channelType == TYPE_UNORM8) { + /* + * Optimized path for 8bit unorms. + */ + + if (channels == 4) { + for (row = start(); row != end(); row += stride()) { + const uint32_t *src = (const uint32_t *)row; + uint32_t *dst = (uint32_t *)tmp; + unsigned x; + for (x = 0; x + 4 <= width; x += 4) { + /* + * It's much faster to access dwords than bytes. + * + * FIXME: Big-endian version. + */ + + uint32_t rgba0 = *src++ & 0xffffff; + uint32_t rgba1 = *src++ & 0xffffff; + uint32_t rgba2 = *src++ & 0xffffff; + uint32_t rgba3 = *src++ & 0xffffff; + uint32_t rgb0 = rgba0 + | (rgba1 << 24); + uint32_t rgb1 = (rgba1 >> 8) + | (rgba2 << 16); + uint32_t rgb2 = (rgba2 >> 16) + | (rgba3 << 8); + *dst++ = rgb0; + *dst++ = rgb1; + *dst++ = rgb2; + } + for (; x < width; ++x) { + tmp[x*3 + 0] = row[x*4 + 0]; + tmp[x*3 + 1] = row[x*4 + 1]; + tmp[x*3 + 2] = row[x*4 + 2]; + } + os.write((const char *)tmp, width*3); } - for (; x < width; ++x) { - tmp[x*3 + 0] = row[x*4 + 0]; - tmp[x*3 + 1] = row[x*4 + 1]; - tmp[x*3 + 2] = row[x*4 + 2]; + } else if (channels == 2) { + for (row = start(); row != end(); row += stride()) { + const unsigned char *src = row; + unsigned char *dst = tmp; + for (unsigned x = 0; x < width; ++x) { + *dst++ = *src++; + *dst++ = *src++; + *dst++ = 0; + } + os.write((const char *)tmp, width*3); } - os.write((const char *)tmp, width*3); + } else { + assert(0); } - } else if (channels == 2) { + } else { + /* + * General path for float images. + */ + + assert(channelType == TYPE_FLOAT); + for (row = start(); row != end(); row += stride()) { - const unsigned char *src = row; - unsigned char *dst = tmp; + const float *src = (const float *)row; + float *dst = (float *)tmp; for (unsigned x = 0; x < width; ++x) { - *dst++ = *src++; - *dst++ = *src++; - *dst++ = 0; + unsigned channel = 0; + for (; channel < channels; ++channel) { + *dst++ = *src++; + } + for (; channel < channels; ++channel) { + *dst++ = 0; + } } - os.write((const char *)tmp, width*3); + os.write((const char *)tmp, width*bytesPerPixel); } - } else { - assert(0); } + delete [] tmp; } } diff --git a/image/image_raw.cpp b/image/image_raw.cpp index 93946be..9e8fbfd 100644 --- a/image/image_raw.cpp +++ b/image/image_raw.cpp @@ -41,10 +41,12 @@ namespace image { void Image::writeRAW(std::ostream &os) const { + assert(channelType == TYPE_UNORM8); + const unsigned char *row; for (row = start(); row != end(); row += stride()) { - os.write((const char *)row, width*channels); + os.write((const char *)row, width*channels*bytesPerPixel); } } diff --git a/scripts/retracediff.py b/scripts/retracediff.py index 4ed5837..89aac5a 100755 --- a/scripts/retracediff.py +++ b/scripts/retracediff.py @@ -43,9 +43,9 @@ import jsondiff # Null file, to use when we're not interested in subprocesses output if platform.system() == 'Windows': - NULL = open('NUL:', 'wt') + NULL = open('NUL:', 'wb') else: - NULL = open('/dev/null', 'wt') + NULL = open('/dev/null', 'wb') class RetraceRun: @@ -134,9 +134,19 @@ def read_pnm(stream): magic = magic.rstrip() if magic == 'P5': channels = 1 + bytesPerChannel = 1 mode = 'L' elif magic == 'P6': channels = 3 + bytesPerChannel = 1 + mode = 'RGB' + elif magic == 'Pf': + channels = 1 + bytesPerChannel = 4 + mode = 'R' + elif magic == 'PF': + channels = 3 + bytesPerChannel = 4 mode = 'RGB' else: raise Exception('Unsupported magic `%s`' % magic) @@ -146,9 +156,18 @@ def read_pnm(stream): comment += line[1:] line = stream.readline() width, height = map(int, line.strip().split()) - maximum = int(stream.readline().strip()) - assert maximum == 255 - data = stream.read(height * width * channels) + if bytesPerChannel == 1: + maximum = int(stream.readline().strip()) + assert maximum == 255 + data = stream.read(height * width * channels * bytesPerChannel) + if magic == 'PF': + # XXX: Image magic only supports single channel floating point images, + # so convert to 8bit RGB + pixels = array('f', data) + pixels *= 255 + pixels = array('B', pixels) + data = pixels.tostring() + image = Image.frombuffer(mode, (width, height), data, 'raw', mode, 0, 1) return image, comment -- 2.43.0