/* This file is part of Mitsuba, a physically based rendering system. Copyright (c) 2007-2014 by Wenzel Jakob and others. Mitsuba is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License Version 3 as published by the Free Software Foundation. Mitsuba is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include #include #if defined(__WINDOWS__) #undef _CRT_SECURE_NO_WARNINGS #define _MATH_DEFINES_DEFINED #endif #if defined(MTS_HAS_OPENEXR) #if defined(_MSC_VER) #pragma warning(disable : 4231) // nonstandard extension used : 'extern' before template explicit instantiation #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif #if defined(MTS_HAS_LIBPNG) #include #endif #if defined(MTS_HAS_LIBJPEG) extern "C" { #include #include }; #endif #if defined(MTS_HAS_FFTW) #include #include #endif MTS_NAMESPACE_BEGIN namespace { // Safely convert between scalar types avoiding downcasting warning template inline T safe_cast(S a) { return static_cast(a); } template <> inline half safe_cast(double a) { return static_cast(static_cast(a)); } } #if defined(MTS_HAS_OPENEXR) /* ========================== * * EXR helper classes * * ========================== */ class EXRIStream : public Imf::IStream { public: EXRIStream(Stream *stream) : IStream(stream->toString().c_str()), m_stream(stream) { m_offset = stream->getPos(); m_size = stream->getSize(); } bool read(char *c, int n) { m_stream->read(c, n); return m_stream->getPos() == m_size; } Imf::Int64 tellg() { return m_stream->getPos()-m_offset; } void seekg(Imf::Int64 pos) { m_stream->seek((size_t) pos + m_offset); } void clear() { } private: ref m_stream; size_t m_offset, m_size; }; class EXROStream : public Imf::OStream { public: EXROStream(Stream *stream) : OStream(stream->toString().c_str()), m_stream(stream) { } void write(const char *c, int n) { m_stream->write(c, n); } Imf::Int64 tellp() { return m_stream->getPos(); } void seekp(Imf::Int64 pos) { m_stream->seek((size_t) pos); } void clear() { } private: ref m_stream; }; inline bool chromaticitiesMatch(const Imf::Chromaticities &a, const Imf::Chromaticities &b) { Float diff2 = (a.red-b.red).length2() + (a.green-b.green).length2() + (a.blue-b.blue).length2() + (a.white-b.white).length2(); return diff2 < 1e-8f; } #endif #if defined(MTS_HAS_LIBPNG) /* ========================== * * PNG helper functions * * ========================== */ static void png_flush_data(png_structp png_ptr) { png_voidp flush_io_ptr = png_get_io_ptr(png_ptr); ((Stream *) flush_io_ptr)->flush(); } static void png_read_data(png_structp png_ptr, png_bytep data, png_size_t length) { png_voidp read_io_ptr = png_get_io_ptr(png_ptr); ((Stream *) read_io_ptr)->read(data, length); } static void png_write_data(png_structp png_ptr, png_bytep data, png_size_t length) { png_voidp write_io_ptr = png_get_io_ptr(png_ptr); ((Stream *) write_io_ptr)->write(data, length); } static void png_error_func(png_structp png_ptr, png_const_charp msg) { SLog(EError, "Fatal libpng error: %s\n", msg); exit(-1); } static void png_warn_func(png_structp png_ptr, png_const_charp msg) { if (strstr(msg, "iCCP: known incorrect sRGB profile") != NULL) return; SLog(EWarn, "libpng warning: %s\n", msg); } #endif #if defined(MTS_HAS_LIBJPEG) /* ========================== * * JPEG helper functions * * ========================== */ extern "C" { static const size_t jpeg_bufferSize = 0x8000; typedef struct { struct jpeg_source_mgr mgr; JOCTET * buffer; mitsuba::Stream *stream; } jbuf_in_t; typedef struct { struct jpeg_destination_mgr mgr; JOCTET * buffer; mitsuba::Stream *stream; } jbuf_out_t; METHODDEF(void) jpeg_init_source(j_decompress_ptr cinfo) { jbuf_in_t *p = (jbuf_in_t *) cinfo->src; p->buffer = new JOCTET[jpeg_bufferSize]; } METHODDEF(boolean) jpeg_fill_input_buffer (j_decompress_ptr cinfo) { jbuf_in_t *p = (jbuf_in_t *) cinfo->src; size_t nBytes; try { p->stream->read(p->buffer, jpeg_bufferSize); nBytes = jpeg_bufferSize; } catch (const EOFException &e) { nBytes = e.getCompleted(); if (nBytes == 0) { /* Insert a fake EOI marker */ p->buffer[0] = (JOCTET) 0xFF; p->buffer[1] = (JOCTET) JPEG_EOI; nBytes = 2; } } cinfo->src->bytes_in_buffer = nBytes; cinfo->src->next_input_byte = p->buffer; return TRUE; } METHODDEF(void) jpeg_skip_input_data (j_decompress_ptr cinfo, long num_bytes) { if (num_bytes > 0) { while (num_bytes > (long) cinfo->src->bytes_in_buffer) { num_bytes -= (long) cinfo->src->bytes_in_buffer; jpeg_fill_input_buffer(cinfo); } cinfo->src->next_input_byte += (size_t) num_bytes; cinfo->src->bytes_in_buffer -= (size_t) num_bytes; } } METHODDEF(void) jpeg_term_source (j_decompress_ptr cinfo) { jbuf_in_t *p = (jbuf_in_t *) cinfo->src; delete[] p->buffer; } METHODDEF(void) jpeg_init_destination(j_compress_ptr cinfo) { jbuf_out_t *p = (jbuf_out_t *)cinfo->dest; p->buffer = new JOCTET[jpeg_bufferSize]; p->mgr.next_output_byte = p->buffer; p->mgr.free_in_buffer = jpeg_bufferSize; } METHODDEF(boolean) jpeg_empty_output_buffer(j_compress_ptr cinfo) { jbuf_out_t *p = (jbuf_out_t *)cinfo->dest; p->stream->write(p->buffer, jpeg_bufferSize); p->mgr.next_output_byte = p->buffer; p->mgr.free_in_buffer = jpeg_bufferSize; return 1; } METHODDEF(void) jpeg_term_destination(j_compress_ptr cinfo) { jbuf_out_t *p = (jbuf_out_t *)cinfo->dest; p->stream->write(p->buffer, jpeg_bufferSize-p->mgr.free_in_buffer); delete[] p->buffer; p->mgr.free_in_buffer = 0; } METHODDEF(void) jpeg_error_exit (j_common_ptr cinfo) throw(std::runtime_error) { char msg[JMSG_LENGTH_MAX]; (*cinfo->err->format_message) (cinfo, msg); SLog(EError, "Critcal libjpeg error: %s", msg); } }; #endif /* ========================== * * Bitmap class * * ========================== */ Bitmap::Bitmap(EPixelFormat pFormat, EComponentFormat cFormat, const Vector2i &size, uint8_t channelCount, uint8_t *data) : m_pixelFormat(pFormat), m_componentFormat(cFormat), m_size(size), m_data(data), m_channelCount(channelCount), m_ownsData(false) { AssertEx(size.x > 0 && size.y > 0, "Invalid bitmap size"); if (m_componentFormat == EUInt8) m_gamma = -1.0f; // sRGB by default else m_gamma = 1.0f; // Linear by default updateChannelCount(); if (!m_data) { m_data = static_cast(allocAligned(getBufferSize())); m_ownsData = true; } } Bitmap::Bitmap(EFileFormat format, Stream *stream, const std::string &prefix) : m_data(NULL), m_ownsData(false) { readStream(format, stream, prefix); } Bitmap::Bitmap(const fs::path &path, const std::string &prefix) : m_data(NULL), m_ownsData(false) { ref fs = new FileStream(path, FileStream::EReadOnly); readStream(EAuto, fs, prefix); } void Bitmap::readStream(EFileFormat format, Stream *stream, const std::string &prefix) { if (format == EAuto) { /* Try to automatically detect the file format */ size_t pos = stream->getPos(); uint8_t start[8]; stream->read(start, 8); if (start[0] == 'B' && start[1] == 'M') { format = EBMP; } else if (start[0] == '#' && start[1] == '?') { format = ERGBE; } else if (start[0] == 'P' && (start[1] == 'F' || start[1] == 'f')) { format = EPFM; } else if (start[0] == 'P' && start[1] == '6') { format = EPPM; #if defined(MTS_HAS_LIBJPEG) } else if (start[0] == 0xFF && start[1] == 0xD8) { format = EJPEG; #endif #if defined(MTS_HAS_LIBPNG) } else if (png_sig_cmp(start, 0, 8) == 0) { format = EPNG; #endif #if defined(MTS_HAS_OPENEXR) } else if (Imf::isImfMagic((const char *) start)) { format = EOpenEXR; #endif } else { /* Check for a TGAv2 file */ char footer[18]; stream->seek(stream->getSize() - 18); stream->read(footer, 18); if (footer[17] == 0 && strncmp(footer, "TRUEVISION-XFILE.", 17) == 0) format = ETGA; } stream->seek(pos); } switch (format) { case EBMP: readBMP(stream); break; case EJPEG: readJPEG(stream); break; case EOpenEXR: readOpenEXR(stream, prefix); break; case ERGBE: readRGBE(stream); break; case EPFM: readPFM(stream); break; case EPPM: readPPM(stream); break; case ETGA: readTGA(stream); break; case EPNG: readPNG(stream); break; default: Log(EError, "Bitmap: Invalid file format!"); } } void Bitmap::write(const fs::path &path, int compression) const { std::string s = boost::to_lower_copy(path.string()); EFileFormat format; if (boost::ends_with(s, "jpeg") || boost::ends_with(s, "jpg")) format = EJPEG; else if (boost::ends_with(s, "png")) format = EPNG; else if (boost::ends_with(s, "exr")) format = EOpenEXR; else if (boost::ends_with(s, "hdr") || boost::ends_with(s, "rgbe")) format = ERGBE; else if (boost::ends_with(s, "pfm")) format = EPFM; else if (boost::ends_with(s, "ppm")) format = EPPM; else { Log(EError, "No supported bitmap file extension: \"%s\"", path.string().c_str()); return; } write(format, path, compression); } void Bitmap::write(EFileFormat format, const fs::path &path, int compression) const { ref fs = new FileStream(path, FileStream::ETruncReadWrite); write(format, fs, compression); } void Bitmap::write(EFileFormat format, Stream *stream, int compression) const { switch (format) { case EJPEG: if (compression == -1) compression = 100; writeJPEG(stream, compression); break; case EPNG: if (compression == -1) compression = 5; writePNG(stream, compression); break; case EOpenEXR: writeOpenEXR(stream); break; case ERGBE: writeRGBE(stream); break; case EPFM: writePFM(stream); break; case EPPM: writePPM(stream); break; default: Log(EError, "Bitmap::write(): Invalid file format!"); } } size_t Bitmap::getBufferSize() const { size_t bitsPerRow = (size_t) m_size.x * m_channelCount * getBitsPerComponent(); size_t bytesPerRow = (bitsPerRow + 7) / 8; // round up to full bytes return bytesPerRow * (size_t) m_size.y; } std::string Bitmap::getChannelName(int idx) const { Assert(idx < m_channelCount); char name = '\0'; switch (m_pixelFormat) { case ELuminance: name = 'L'; break; case ELuminanceAlpha: name = "YA"[idx]; break; case ERGBA: case ERGB: name = "RGBA"[idx]; break; case EXYZA: case EXYZ: name = "XYZA"[idx]; break; case ESpectrumAlphaWeight: case ESpectrumAlpha: if (idx == m_channelCount-1) return m_pixelFormat == ESpectrumAlpha ? "A" : "W"; else if (idx == m_channelCount-2 && m_pixelFormat == ESpectrumAlphaWeight) return "A"; case ESpectrum: { std::pair coverage = Spectrum::getBinCoverage(idx); return formatString("%.2f-%.2fnm", coverage.first, coverage.second); } default: Log(EError, "Unknown pixel format!"); } return std::string(1, name); } void Bitmap::setChannelNames(const std::vector &names) { if (!names.empty() && names.size() != m_channelCount) Log(EError, "setChannelNames(): tried to set %i channel names for an image with %i channels!", (int) names.size(), m_channelCount); m_channelNames = names; } void Bitmap::updateChannelCount() { switch (m_pixelFormat) { case ELuminance: m_channelCount = 1; break; case ELuminanceAlpha: m_channelCount = 2; break; case ERGB: m_channelCount = 3; break; case ERGBA: m_channelCount = 4; break; case EXYZ: m_channelCount = 3; break; case EXYZA: m_channelCount = 4; break; case ESpectrum: m_channelCount = SPECTRUM_SAMPLES; break; case ESpectrumAlpha: m_channelCount = SPECTRUM_SAMPLES + 1; break; case ESpectrumAlphaWeight: m_channelCount = SPECTRUM_SAMPLES + 2; break; case EMultiSpectrumAlphaWeight: break; case EMultiChannel: break; default: Log(EError, "Unknown pixel format!"); } } int Bitmap::getBitsPerComponent() const { switch (m_componentFormat) { case EBitmask: return 1; break; case EUInt8: return 8; break; case EUInt16: return 16; break; case EUInt32: return 32; break; case EFloat16: return 16; break; case EFloat32: return 32; break; case EFloat64: return 64; break; default: Log(EError, "Unknown component format!"); return -1; } } int Bitmap::getBytesPerComponent() const { switch (m_componentFormat) { case EUInt8: return 1; break; case EUInt16: return 2; break; case EUInt32: return 4; break; case EFloat16: return 2; break; case EFloat32: return 4; break; case EFloat64: return 8; break; case EBitmask: Log(EError, "Bitmask images have less than 1 byte per component!"); return -1; default: Log(EError, "Unknown component format!"); return -1; } } Bitmap::~Bitmap() { if (m_data && m_ownsData) freeAligned(m_data); } void Bitmap::clear() { memset(m_data, 0, getBufferSize()); } ref Bitmap::clone() const { ref bitmap = new Bitmap(m_pixelFormat, m_componentFormat, m_size); memcpy(bitmap->m_data, m_data, getBufferSize()); bitmap->m_metadata = m_metadata; bitmap->m_gamma = m_gamma; bitmap->m_channelNames = m_channelNames; return bitmap; } void Bitmap::flipVertically() { if (m_componentFormat == EBitmask) Log(EError, "Transformations involving bitmasks are currently not supported!"); size_t rowSize = getBufferSize() / m_size.y; int halfHeight = m_size.y / 2; uint8_t *temp = (uint8_t *) alloca(rowSize); for (int i=0, j=m_size.y-1; i Bitmap::rotateFlip(ERotateFlipType type) const { /* Based on the GDI+ rotate/flip function in Wine */ if (m_componentFormat == EBitmask) Log(EError, "Transformations involving bitmasks are currently not supported!"); int width = m_size.x, height = m_size.y; bool flip_x = (type & 6) == 2 || (type & 6) == 4; bool flip_y = (type & 3) == 1 || (type & 3) == 2; bool rotate_90 = type & 1; if (rotate_90) std::swap(width, height); ref result = new Bitmap(m_pixelFormat, m_componentFormat, Vector2i(width, height), m_channelCount); ssize_t bypp = getBytesPerPixel(), src_stride = m_size.x * bypp, dst_stride = width * bypp; uint8_t *dst = result->getUInt8Data(); uint8_t *dst_row = dst, *src_row = m_data; if (flip_x) src_row += bypp * (m_size.x - 1); if (flip_y) src_row += src_stride * (m_size.y - 1); ssize_t src_x_step, src_y_step; if (rotate_90) { src_x_step = flip_y ? -src_stride : src_stride; src_y_step = flip_x ? -bypp : bypp; } else { src_x_step = flip_x ? -bypp : bypp; src_y_step = flip_y ? -src_stride : src_stride; } for (int y=0; ygetPixelFormat() && getComponentFormat() == bitmap->getComponentFormat() && getChannelCount() == bitmap->getChannelCount()); Vector2i offsetIncrease( std::max(0, std::max(-sourceOffset.x, -targetOffset.x)), std::max(0, std::max(-sourceOffset.y, -targetOffset.y)) ); sourceOffset += offsetIncrease; targetOffset += offsetIncrease; size -= offsetIncrease; Vector2i sizeDecrease( std::max(0, std::max(sourceOffset.x + size.x - bitmap->getWidth(), targetOffset.x + size.x - getWidth())), std::max(0, std::max(sourceOffset.y + size.y - bitmap->getHeight(), targetOffset.y + size.y - getHeight()))); size -= sizeDecrease; if (size.x <= 0 || size.y <= 0) return; const size_t pixelStride = getBytesPerPixel(), sourceStride = bitmap->getWidth() * pixelStride, targetStride = getWidth() * pixelStride; const uint8_t *source = bitmap->getUInt8Data() + (sourceOffset.x + sourceOffset.y * (size_t) bitmap->getWidth()) * pixelStride; uint8_t *target = m_data + (targetOffset.x + targetOffset.y * (size_t) m_size.x) * pixelStride; for (int y = 0; y < size.y; ++y) { memcpy(target, source, size.x * getBytesPerPixel()); source += sourceStride; target += targetStride; } } void Bitmap::accumulate(const Bitmap *bitmap, Point2i sourceOffset, Point2i targetOffset, Vector2i size) { Assert(getPixelFormat() == bitmap->getPixelFormat() && getComponentFormat() == bitmap->getComponentFormat() && getChannelCount() == bitmap->getChannelCount()); Vector2i offsetIncrease( std::max(0, std::max(-sourceOffset.x, -targetOffset.x)), std::max(0, std::max(-sourceOffset.y, -targetOffset.y)) ); sourceOffset += offsetIncrease; targetOffset += offsetIncrease; size -= offsetIncrease; Vector2i sizeDecrease( std::max(0, std::max(sourceOffset.x + size.x - bitmap->getWidth(), targetOffset.x + size.x - getWidth())), std::max(0, std::max(sourceOffset.y + size.y - bitmap->getHeight(), targetOffset.y + size.y - getHeight()))); size -= sizeDecrease; if (size.x <= 0 || size.y <= 0) return; const size_t columns = (size_t) size.x * m_channelCount, pixelStride = getBytesPerPixel(), sourceStride = bitmap->getWidth() * pixelStride, targetStride = getWidth() * pixelStride; const uint8_t *source = bitmap->getUInt8Data() + (sourceOffset.x + sourceOffset.y * (size_t) bitmap->getWidth()) * pixelStride; uint8_t *target = m_data + (targetOffset.x + targetOffset.y * (size_t) m_size.x) * pixelStride; for (int y = 0; y < size.y; ++y) { switch (m_componentFormat) { case EUInt8: for (size_t i = 0; i < columns; ++i) ((uint8_t *) target)[i] = (uint8_t) std::min(0xFF, ((uint8_t *) source)[i] + ((uint8_t *) target)[i]); break; case EUInt16: for (size_t i = 0; i < columns; ++i) ((uint16_t *) target)[i] = (uint16_t) std::min(0xFFFF, ((uint16_t *) source)[i] + ((uint16_t *) target)[i]); break; case EUInt32: for (size_t i = 0; i < columns; ++i) ((uint32_t *) target)[i] = std::min((uint32_t) 0xFFFFFFFFUL, ((uint32_t *) source)[i] + ((uint32_t *) target)[i]); break; case EFloat16: for (size_t i = 0; i < columns; ++i) ((half *) target)[i] += ((half *) source)[i]; break; case EFloat32: for (size_t i = 0; i < columns; ++i) ((float *) target)[i] += ((float *) source)[i]; break; case EFloat64: for (size_t i = 0; i < columns; ++i) ((double *) target)[i] += ((double *) source)[i]; break; default: Log(EError, "Unknown component format!"); } source += sourceStride; target += targetStride; } } Spectrum Bitmap::average() const { if (m_gamma != 1 || (m_componentFormat != EFloat16 && m_componentFormat != EFloat32 && m_componentFormat != EFloat64)) Log(EError, "Bitmap::average() assumes a floating point image with linear gamma!"); size_t pixelCount = (size_t) m_size.x * (size_t) m_size.y; Float *accum = new Float[m_channelCount]; memset(accum, 0, sizeof(Float) * m_channelCount); switch (m_componentFormat) { case EFloat16: { const half *ptr = getFloat16Data(); for (size_t i=0; iconvert(m_pixelFormat, 1.0f, accum, ESpectrum, 1.0f, &result, 1); delete[] accum; return result; } #if defined(MTS_HAS_FFTW) static boost::mutex __fftw_lock; #endif void Bitmap::convolve(const Bitmap *_kernel) { if (_kernel->getWidth() != _kernel->getHeight()) Log(EError, "Bitmap::convolve(): convolution kernel must be square!"); if (_kernel->getWidth() % 2 != 1) Log(EError, "Bitmap::convolve(): convolution kernel size must be odd!"); if (_kernel->getChannelCount() != getChannelCount() && _kernel->getChannelCount() != 1) Log(EError, "Bitmap::convolve(): kernel and bitmap have different channel counts!"); if (_kernel->getComponentFormat() != getComponentFormat()) Log(EError, "Bitmap::convolve(): kernel and bitmap have different component formats!"); if (m_componentFormat != EFloat16 && m_componentFormat != EFloat32 && m_componentFormat != EFloat64) Log(EError, "Bitmap::convolve(): unsupported component format! (must be float16/float32/float64)"); int channelCountKernel = _kernel->getChannelCount(); size_t kernelSize = (size_t) _kernel->getWidth(), hKernelSize = kernelSize / 2, width = (size_t) m_size.x, height = (size_t) m_size.y; #if defined(MTS_HAS_FFTW) typedef std::complex complex; size_t paddedWidth = width + hKernelSize, paddedHeight = height + hKernelSize, paddedSize = paddedWidth*paddedHeight; __fftw_lock.lock(); complex *kernel = (complex *) fftw_malloc(sizeof(complex) * paddedSize), *kernelS = (complex *) fftw_malloc(sizeof(complex) * paddedSize), *data = (complex *) fftw_malloc(sizeof(complex) * paddedSize), *dataS = (complex *) fftw_malloc(sizeof(complex) * paddedSize); if (!kernel || !kernelS || !data || !dataS) { __fftw_lock.unlock(); SLog(EError, "Bitmap::convolve(): Unable to allocate temporary memory!"); } /* Create a FFTW plan for a 2D DFT of this size */ fftw_plan p = fftw_plan_dft_2d((int) paddedHeight, (int) paddedWidth, (fftw_complex *) kernel, (fftw_complex *) kernelS, FFTW_FORWARD, FFTW_ESTIMATE); __fftw_lock.unlock(); memset(kernel, 0, sizeof(complex)*paddedSize); for (int ch=0; chgetFloat16Data()[(x+y*kernelSize)*channelCountKernel+ch]; } } } /* Copy and zero-pad the input data */ for (size_t y=0; ygetFloat32Data()[(x+y*kernelSize)*channelCountKernel+ch]; } } } /* Copy and zero-pad the input data */ for (size_t y=0; ygetFloat64Data()[(x+y*kernelSize)*channelCountKernel+ch]; } } } /* Copy and zero-pad the input data */ for (size_t y=0; y(allocAligned(getBufferSize())); for (int ch=0; ch 1 ? ch : 0; switch (m_componentFormat) { case EFloat16: { const half *input = getFloat16Data(); const half *kernel = _kernel->getFloat16Data(); half *output = (half *) output_; for (size_t y=0; y= 0 && ys >= 0 && xs < (ssize_t) width && ys < (ssize_t) height) result += (double) kernel[(kx+ky*kernelSize)*channelCountKernel+chKernel] * (double) input[(xs+ys*width)*m_channelCount+ch]; } } output[(x+y*width)*m_channelCount+ch] = safe_cast(result); } } } break; case EFloat32: { const float *input = getFloat32Data(); const float *kernel = _kernel->getFloat32Data(); float *output = (float *) output_; for (size_t y=0; y= 0 && ys >= 0 && xs < (ssize_t) width && ys < (ssize_t) height) result += kernel[(kx+ky*kernelSize)*channelCountKernel+chKernel] * input[(xs+ys*width)*m_channelCount+ch]; } } output[(x+y*width)*m_channelCount+ch] = (float) result; } } } break; case EFloat64: { const double *input = getFloat64Data(); const double *kernel = _kernel->getFloat64Data(); double *output = (double *) output_; for (size_t y=0; y= 0 && ys >= 0 && xs < (ssize_t) width && ys < (ssize_t) height) result += kernel[(kx+ky*kernelSize)*channelCountKernel+chKernel] * input[(xs+ys*width)*m_channelCount+ch]; } } output[(x+y*width)*m_channelCount+ch] = result; } } } break; default: Log(EError, "Unsupported component format!"); } } if (m_ownsData) freeAligned(m_data); m_data = output_; m_ownsData = true; #endif } void Bitmap::scale(Float value) { if (m_componentFormat == EBitmask) Log(EError, "Bitmap::scale(): bitmasks are not supported!"); size_t nPixels = getPixelCount(), nChannels = getChannelCount(); if (hasAlpha()) { switch (m_componentFormat) { case EUInt8: { uint8_t *data = (uint8_t *) m_data; for (size_t i=0; i (*data * value); ++data; } ++data; } } break; case EFloat32: { float *data = (float *) m_data; for (size_t i=0; i (data[i] * value); } break; case EFloat32: { float *data = (float *) m_data; for (size_t i=0; i (std::pow((Float) *data, value)); ++data; } ++data; } } break; case EFloat32: { float *data = (float *) m_data; for (size_t i=0; i (std::pow((Float) data[i], value)); } break; case EFloat32: { float *data = (float *) m_data; for (size_t i=0; i Bitmap::arithmeticOperation(Bitmap::EArithmeticOperation operation, const Bitmap *_bitmap1, const Bitmap *_bitmap2) { ref bitmap1(_bitmap1), bitmap2(_bitmap2); /* Determine the 'fancier' pixel / component format by a maximum operation on the enum values */ EPixelFormat pxFmt = (EPixelFormat) std::max(bitmap1->getPixelFormat(), bitmap2->getPixelFormat()); EComponentFormat cFmt = (EComponentFormat) std::max(bitmap1->getComponentFormat(), bitmap2->getComponentFormat()); if (cFmt == EBitmask) Log(EError, "Bitmap::arithmeticOperation(): bitmasks are not supported!"); /* Make sure that the images match in size (resample if necessary) */ Vector2i size( std::max(bitmap1->getWidth(), bitmap2->getWidth()), std::max(bitmap1->getHeight(), bitmap2->getHeight())); if (bitmap1->getSize() != size) { bitmap1 = bitmap1->resample(NULL, ReconstructionFilter::EClamp, ReconstructionFilter::EClamp, size, -std::numeric_limits::infinity(), std::numeric_limits::infinity()); } if (bitmap2->getSize() != size) { bitmap2 = bitmap2->resample(NULL, ReconstructionFilter::EClamp, ReconstructionFilter::EClamp, size, -std::numeric_limits::infinity(), std::numeric_limits::infinity()); } /* Convert the image format appropriately (no-op, if the format already matches) */ bitmap1 = const_cast(bitmap1.get())->convert(pxFmt, cFmt); bitmap2 = const_cast(bitmap2.get())->convert(pxFmt, cFmt); ref output = new Bitmap(pxFmt, cFmt, size); size_t nValues = output->getPixelCount() * output->getChannelCount(); #define IMPLEMENT_OPS() \ switch (operation) { \ case EAddition: for (size_t i=0; igetUInt8Data(); const uint8_t *src2 = bitmap2->getUInt8Data(); uint8_t *dst = output->getUInt8Data(); IMPLEMENT_OPS(); } break; case EUInt16: { const uint16_t *src1 = bitmap1->getUInt16Data(); const uint16_t *src2 = bitmap2->getUInt16Data(); uint16_t *dst = output->getUInt16Data(); IMPLEMENT_OPS(); } break; case EUInt32: { const uint32_t *src1 = bitmap1->getUInt32Data(); const uint32_t *src2 = bitmap2->getUInt32Data(); uint32_t *dst = output->getUInt32Data(); IMPLEMENT_OPS(); } break; case EFloat16: { const half *src1 = bitmap1->getFloat16Data(); const half *src2 = bitmap2->getFloat16Data(); half *dst = output->getFloat16Data(); IMPLEMENT_OPS(); } break; case EFloat32: { const float *src1 = bitmap1->getFloat32Data(); const float *src2 = bitmap2->getFloat32Data(); float *dst = output->getFloat32Data(); IMPLEMENT_OPS(); } break; case EFloat64: { const double *src1 = bitmap1->getFloat64Data(); const double *src2 = bitmap2->getFloat64Data(); double *dst = output->getFloat64Data(); IMPLEMENT_OPS(); } break; default: Log(EError, "Bitmap::arithmeticOperation(): unexpected data format!"); } #undef IMPLEMENT_OPS return output; } void Bitmap::colorBalance(Float r, Float g, Float b) { if (m_pixelFormat != ERGB && m_pixelFormat != ERGBA) Log(EError, "colorBalance(): expected a RGB or RGBA image!"); int stride = m_pixelFormat == ERGB ? 3 : 4; size_t pixelCount = (size_t) m_size.x * (size_t) m_size.y; switch (m_componentFormat) { case EFloat16: { half *ptr = getFloat16Data(); for (size_t i=0; i= 0 && pos.x < m_size.x && pos.y >= 0 && pos.y < m_size.y, "Bitmap::setPixel(): out of bounds!"); size_t offset = ((size_t) pos.x + m_size.x * (size_t) pos.y) * getBytesPerPixel(); const FormatConverter *cvt = FormatConverter::getInstance( std::make_pair(EFloat, m_componentFormat) ); cvt->convert(ESpectrum, 1.0f, &value, m_pixelFormat, m_gamma, m_data + offset, 1); } void Bitmap::drawHLine(int y, int x1, int x2, const Spectrum &value) { if (y < 0 || y >= m_size.y) return; x1 = std::max(x1, 0); x2 = std::min(x2, m_size.x-1); const FormatConverter *cvt = FormatConverter::getInstance( std::make_pair(EFloat, m_componentFormat) ); size_t pixelStride = getBytesPerPixel(); uint8_t *source = (uint8_t *) alloca(pixelStride); cvt->convert(ESpectrum, 1.0f, &value, m_pixelFormat, m_gamma, source, 1); uint8_t *target = m_data + (x1 + y*m_size.x) * pixelStride; for (int x=x1; x<=x2; ++x) { memcpy(target, source, pixelStride); target += pixelStride; } } void Bitmap::drawVLine(int x, int y1, int y2, const Spectrum &value) { if (x < 0 || x >= m_size.x) return; y1 = std::max(y1, 0); y2 = std::min(y2, m_size.y-1); const FormatConverter *cvt = FormatConverter::getInstance( std::make_pair(EFloat, m_componentFormat) ); size_t pixelStride = getBytesPerPixel(), rowStride = pixelStride * m_size.x; uint8_t *source = (uint8_t *) alloca(pixelStride); cvt->convert(ESpectrum, 1.0f, &value, m_pixelFormat, m_gamma, source, 1); uint8_t *target = m_data + (x + y1*m_size.x) * pixelStride; for (int y=y1; y<=y2; ++y) { memcpy(target, source, pixelStride); target += rowStride; } } void Bitmap::drawRect(const Point2i &offset, const Vector2i &size, const Spectrum &value) { drawHLine(offset.y, offset.x, offset.x + size.x - 1, value); drawHLine(offset.y + size.y - 1, offset.x, offset.x + size.x - 1, value); drawVLine(offset.x, offset.y, offset.y + size.y - 1, value); drawVLine(offset.x + size.x - 1, offset.y, offset.y + size.y - 1, value); } void Bitmap::fillRect(Point2i offset, Vector2i size, const Spectrum &value) { int sx = std::max(0, -offset.x), sy = std::max(0, -offset.y); size.x -= sx; size.y -= sy; offset.x += sx; offset.y += sy; size.x -= std::max(0, offset.x + size.x - m_size.x); size.y -= std::max(0, offset.y + size.y - m_size.y); const FormatConverter *cvt = FormatConverter::getInstance( std::make_pair(EFloat, m_componentFormat) ); size_t pixelStride = getBytesPerPixel(), rowStride = pixelStride * m_size.x; uint8_t *source = (uint8_t *) alloca(pixelStride); cvt->convert(ESpectrum, 1.0f, &value, m_pixelFormat, m_gamma, source, 1); uint8_t *target = m_data + (offset.x + offset.y*m_size.x) * pixelStride; for (int y=0; y= 0 && pos.x < m_size.x && pos.y >= 0 && pos.y < m_size.y, "Bitmap::getPixel(): out of bounds!"); size_t offset = ((size_t) pos.x + m_size.x * (size_t) pos.y) * getBytesPerPixel(); const FormatConverter *cvt = FormatConverter::getInstance( std::make_pair(m_componentFormat, EFloat) ); Spectrum result; cvt->convert(m_pixelFormat, m_gamma, m_data + offset, ESpectrum, 1.0f, &result, 1); return result; } void Bitmap::convert(Bitmap *target, Float multiplier, Spectrum::EConversionIntent intent) const { if (m_componentFormat == EBitmask || target->getComponentFormat() == EBitmask) Log(EError, "Conversions involving bitmasks are currently not supported!"); if (m_size != target->getSize()) Log(EError, "Bitmap::convert(): size mismatch!"); if (m_pixelFormat == target->getPixelFormat() && m_componentFormat == target->getComponentFormat() && m_gamma == target->getGamma() && multiplier == 1.0f) { /* No conversion is necessary -- just run memcpy */ memcpy(target->getData(), getData(), getBufferSize()); return; } const FormatConverter *cvt = FormatConverter::getInstance( std::make_pair(m_componentFormat, target->getComponentFormat()) ); Assert(cvt != NULL); cvt->convert(m_pixelFormat, m_gamma, m_data, target->getPixelFormat(), target->getGamma(), target->getData(), (size_t) m_size.x * (size_t) m_size.y, multiplier, intent, m_channelCount); } ref Bitmap::convert(EPixelFormat pixelFormat, EComponentFormat componentFormat, Float gamma, Float multiplier, Spectrum::EConversionIntent intent) { if (m_componentFormat == EBitmask || componentFormat == EBitmask) Log(EError, "Conversions involving bitmasks are currently not supported!"); if (m_pixelFormat == pixelFormat && m_componentFormat == componentFormat && m_gamma == gamma && multiplier == 1.0f) { /* There is nothing to do -- return the current instance */ return this; } const FormatConverter *cvt = FormatConverter::getInstance( std::make_pair(m_componentFormat, componentFormat) ); Assert(cvt != NULL); ref target = new Bitmap(pixelFormat, componentFormat, m_size, m_channelCount); target->setMetadata(m_metadata); if (m_channelNames.size() == (size_t) target->getChannelCount()) target->setChannelNames(m_channelNames); target->setGamma(gamma); cvt->convert(m_pixelFormat, m_gamma, m_data, pixelFormat, gamma, target->getData(), (size_t) m_size.x * (size_t) m_size.y, multiplier, intent, m_channelCount); return target; } ref Bitmap::convertMultiSpectrumAlphaWeight(const std::vector &pixelFormats, EComponentFormat componentFormat, const std::vector &channelNames) const { if (channelNames.size() > std::numeric_limits::max()) Log(EError, "convertMultiSpectrumAlphaWeight(): excessive number of channels!"); ref bitmap = new Bitmap(Bitmap::EMultiChannel, componentFormat, m_size, (uint8_t) channelNames.size()); bitmap->setChannelNames(channelNames); convertMultiSpectrumAlphaWeight(this, getUInt8Data(), bitmap, bitmap->getUInt8Data(), pixelFormats, componentFormat, (size_t) m_size.x * (size_t) m_size.y); return bitmap; } void Bitmap::convertMultiSpectrumAlphaWeight(const Bitmap *source, const uint8_t *sourcePtr, const Bitmap *target, uint8_t *targetPtr, const std::vector &pixelFormats, EComponentFormat componentFormat, size_t count) { if (source->getComponentFormat() != EFloat && source->getPixelFormat() != EMultiSpectrumAlphaWeight) Log(EError, "convertMultiSpectrumAlphaWeight(): unsupported!"); Float *temp = new Float[count * target->getChannelCount()], *dst = temp; for (size_t k = 0; kgetChannelCount(); Float weight = srcData[source->getChannelCount()-1], invWeight = weight == 0 ? 0 : (Float) 1 / weight; Float alpha = srcData[source->getChannelCount()-2] * invWeight; for (size_t i=0; igetComponentFormat()) ); cvt->convert(Bitmap::EMultiChannel, 1.0f, temp, Bitmap::EMultiChannel, 1.0f, targetPtr, count, 1.0f, Spectrum::EReflectance, target->getChannelCount()); delete[] temp; } void Bitmap::convert(void *target, EPixelFormat pixelFormat, EComponentFormat componentFormat, Float gamma, Float multiplier, Spectrum::EConversionIntent intent) const { if (m_componentFormat == EBitmask || componentFormat == EBitmask) Log(EError, "Conversions involving bitmasks are currently not supported!"); if (m_pixelFormat == pixelFormat && m_componentFormat == componentFormat && m_gamma == gamma && multiplier == 1.0f) { /* No conversion is necessary -- just run memcpy */ memcpy(target, getData(), getBufferSize()); return; } const FormatConverter *cvt = FormatConverter::getInstance( std::make_pair(m_componentFormat, componentFormat) ); Assert(cvt != NULL); cvt->convert(m_pixelFormat, m_gamma, m_data, pixelFormat, gamma, target, (size_t) m_size.x * (size_t) m_size.y, multiplier, intent, m_channelCount); } template void tonemapReinhard(T *data, size_t pixels, Bitmap::EPixelFormat fmt, Float &logAvgLuminance, Float &maxLuminance, Float key, Float burn) { int channels = 0; switch (fmt) { case Bitmap::ERGB: case Bitmap::EXYZ: channels = 3; break; case Bitmap::ERGBA: case Bitmap::EXYZA: channels = 4; break; case Bitmap::ELuminanceAlpha: channels = 2; break; case Bitmap::ELuminance: channels = 1; break; default: SLog(EError, "Unsupported pixel format!"); } if (logAvgLuminance <= 0 || maxLuminance <= 0) { /* Compute the log-average luminance if it has not already been provided */ T *ptr = data; maxLuminance = 0; logAvgLuminance = 0; if (fmt == Bitmap::ERGB || fmt == Bitmap::ERGBA) { /* RGB[A] version */ for (size_t i=0; i < pixels; ++i) { Float luminance = (Float) (ptr[0] * (Float) 0.212671 + ptr[1] * (Float) 0.715160 + ptr[2] * (Float) 0.072169); if (luminance == 1024) // ignore the "rendered by mitsuba banner.." maxLuminance = 0.0f; maxLuminance = std::max(maxLuminance, luminance); logAvgLuminance += math::fastlog(1e-3f + luminance); ptr += channels; } } else if (fmt == Bitmap::EXYZ || fmt == Bitmap::EXYZA) { for (size_t i=0; i < pixels; ++i) { Float luminance = (Float) ptr[1]; if (luminance == 1024) // ignore the "rendered by mitsuba banner.." maxLuminance = 0.0f; maxLuminance = std::max(maxLuminance, luminance); logAvgLuminance += math::fastlog(1e-3f + luminance); ptr += channels; } } else { /* Monochrome version */ for (size_t i=0; i < pixels; ++i) { Float luminance = (Float) *ptr; if (luminance == 1024) // ignore the "rendered by mitsuba banner.." maxLuminance = 0.0f; maxLuminance = std::max(maxLuminance, luminance); logAvgLuminance += math::fastlog(1e-3f + luminance); ptr += channels; } } logAvgLuminance = math::fastexp(logAvgLuminance / pixels); } if (maxLuminance == 0) /* This is a black image -- stop now */ return; burn = std::min((Float) 1, std::max((Float) 1e-8f, 1-burn)); Float scale = key / logAvgLuminance, Lwhite = maxLuminance * scale; /* Having the 'burn' parameter scale as 1/b^4 provides a nicely behaved knob */ Float invWp2 = 1 / (Lwhite * Lwhite * std::pow(burn, (Float) 4)); if (fmt == Bitmap::ERGB || fmt == Bitmap::ERGBA) { /* RGB[A] version */ for (size_t i=0; i < pixels; ++i) { /* Convert ITU-R Rec. BT.709 linear RGB to XYZ tristimulus values */ Float X = static_cast(data[0] * 0.412453f + data[1] * 0.357580f + data[2] * 0.180423f); Float Y = static_cast(data[0] * 0.212671f + data[1] * 0.715160f + data[2] * 0.072169f); Float Z = static_cast(data[0] * 0.019334f + data[1] * 0.119193f + data[2] * 0.950227f); /* Convert to xyY */ Float normalization = 1 / (X + Y + Z), x = X * normalization, y = Y * normalization, Lp = Y * scale; /* Apply the tonemapping transformation */ Y = Lp * (1.0f + Lp*invWp2) / (1.0f + Lp); /* Convert back to XYZ */ Float ratio = Y/y; X = ratio * x; Z = ratio * ((Float) 1.0f - x - y); /* Convert from XYZ tristimulus values to ITU-R Rec. BT.709 linear RGB */ data[0] = safe_cast( 3.240479f * X + -1.537150f * Y + -0.498535f * Z); data[1] = safe_cast( -0.969256f * X + 1.875991f * Y + 0.041556f * Z); data[2] = safe_cast( 0.055648f * X + -0.204043f * Y + 1.057311f * Z); data += channels; } } else if (fmt == Bitmap::EXYZ || fmt == Bitmap::EXYZA) { /* XYZ[A] version */ for (size_t i=0; i < pixels; ++i) { Float X = static_cast(data[0]), Y = static_cast(data[1]), Z = static_cast(data[2]); /* Convert to xyY */ Float normalization = 1 / (X + Y + Z), x = X * normalization, y = Y * normalization, Lp = Y * scale; /* Apply the tonemapping transformation */ Y = Lp * (1.0f + Lp*invWp2) / (1.0f + Lp); /* Convert back to XYZ */ Float ratio = Y/y; X = ratio * x; Z = ratio * ((Float) 1.0f - x - y); data[0] = safe_cast(X); data[1] = safe_cast(Y); data[2] = safe_cast(Z); data += channels; } } else { /* Monochrome version */ for (size_t i=0; i < pixels; ++i) { Float Lp = (Float) *data * scale; /* Apply the tonemapping transformation */ *data = safe_cast (Lp * (1.0f + Lp*invWp2) / (1.0f + Lp)); data += channels; } } } void Bitmap::tonemapReinhard(Float &logAvgLuminance, Float &maxLuminance, Float key, Float burn) { Assert(m_pixelFormat == ERGB || m_pixelFormat == ERGBA || m_pixelFormat == ELuminance || m_pixelFormat == ELuminanceAlpha); Assert(m_gamma == 1); size_t pixels = (size_t) m_size.x * (size_t) m_size.y; switch (m_componentFormat) { case EFloat16: mitsuba::tonemapReinhard(getFloat16Data(), pixels, m_pixelFormat, logAvgLuminance, maxLuminance, key, burn); break; case EFloat32: mitsuba::tonemapReinhard(getFloat32Data(), pixels, m_pixelFormat, logAvgLuminance, maxLuminance, key, burn); break; case EFloat64: mitsuba::tonemapReinhard(getFloat64Data(), pixels, m_pixelFormat, logAvgLuminance, maxLuminance, key, burn); break; default: Log(EError, "Bitmap::tonemapReinhard(): Unsupported component format!"); } } std::vector Bitmap::getLayers() const { typedef std::map ChannelMap; if (m_channelNames.empty()) Log(EError, "Bitmap::getLayers(): required color channel names were not available!"); ChannelMap channels; for (size_t i=0; i layers; for (size_t i=0; i= 3 && name[name.length()-2] == '.') { prefix = name.substr(0, name.length() - 1); layer.name = layer.name.substr(0, layer.name.length() - 2); postfix = name[name.length() - 1]; } ChannelMap::iterator itR = channels.find(prefix + "r"), itG = channels.find(prefix + "g"), itB = channels.find(prefix + "b"), itA = channels.find(prefix + "a"), itX = channels.find(prefix + "x"), itY = channels.find(prefix + "y"), itZ = channels.find(prefix + "z"); bool maybeRGB = postfix == 'r' || postfix == 'g' || postfix == 'b' || postfix == 'a'; bool maybeXYZ = postfix == 'x' || postfix == 'y' || postfix == 'z' || postfix == 'a'; bool maybeY = postfix == 'y' || postfix == 'a'; if (maybeRGB && itR != channels.end() && itG != channels.end() && itB != channels.end()) { layer.format = ERGB; layer.channels.push_back(itR->second); layer.channels.push_back(itG->second); layer.channels.push_back(itB->second); if (itA != channels.end()) { layer.format = ERGBA; layer.channels.push_back(itA->second); channels.erase(prefix + "a"); } channels.erase(prefix + "r"); channels.erase(prefix + "g"); channels.erase(prefix + "b"); } else if (maybeXYZ && itX != channels.end() && itY != channels.end() && itZ != channels.end()) { layer.format = EXYZ; layer.channels.push_back(itX->second); layer.channels.push_back(itY->second); layer.channels.push_back(itZ->second); if (itA != channels.end()) { layer.format = EXYZA; layer.channels.push_back(itA->second); channels.erase(prefix + "a"); } channels.erase(prefix + "x"); channels.erase(prefix + "y"); channels.erase(prefix + "z"); } else if (maybeY && itY != channels.end()) { layer.format = ELuminance; layer.channels.push_back(itY->second); if (itA != channels.end()) { layer.format = ELuminanceAlpha; layer.channels.push_back(itA->second); channels.erase(prefix + "a"); } channels.erase(prefix + "y"); } else { if (layer.name.empty()) layer.name = m_channelNames[i]; layer.format = ELuminance; layer.channels.push_back((int) i); channels.erase(name); } layers.push_back(layer); } return layers; } std::map Bitmap::split() const { std::map result; std::vector layers = getLayers(); for (size_t i=0; i channelNames; for (size_t j=0; j _bitmap = this->extractChannels(layer.format, layer.channels); bitmap = _bitmap.get(); bitmap->incRef(); } bitmap->decRef(false); bitmap->setChannelNames(channelNames); if (result.find(layer.name) != result.end()) Log(EError, "Internal error -- encountered two layers with the same name \"%s\"", layer.name.c_str()); result[layer.name] = bitmap; } return result; } ref Bitmap::extractChannels(EPixelFormat fmt, const std::vector &channels) const { int channelCount = getChannelCount(); for (size_t i=0; i= channelCount) Log(EError, "Bitmap::extractChannel(%i): channel index " "must be between 0 and %i", channels[i], channelCount-1); ref result = new Bitmap(fmt, m_componentFormat, m_size, (int) channels.size()); result->setMetadata(m_metadata); result->setGamma(m_gamma); size_t componentSize = getBytesPerComponent(); size_t stride = channelCount * componentSize; size_t pixelCount = (size_t) m_size.x * (size_t) m_size.y; const uint8_t *source = getUInt8Data(); uint8_t *target = result->getUInt8Data(); for (size_t px = 0; px Bitmap::extractChannel(int channelIndex) const { int channelCount = getChannelCount(); if (channelIndex == 0 && channelCount == 1) return const_cast(this); if (channelIndex < 0 || channelIndex >= channelCount) Log(EError, "Bitmap::extractChannel(%i): channel index " "must be between 0 and %i", channelIndex, channelCount-1); ref result = new Bitmap(ELuminance, m_componentFormat, m_size); result->setMetadata(m_metadata); result->setGamma(m_gamma); size_t componentSize = getBytesPerComponent(); size_t offset = channelIndex * componentSize; size_t stride = channelCount * componentSize; size_t pixelCount = (size_t) m_size.x * (size_t) m_size.y; const uint8_t *source = getUInt8Data() + offset; uint8_t *target = result->getUInt8Data(); for (size_t px = 0; px Bitmap::join(EPixelFormat fmt, const std::vector &sourceBitmaps) { if (sourceBitmaps.size() == 0) Log(EError, "Bitmap::join(): Need at least one bitmap!"); const Bitmap *bitmap0 = sourceBitmaps[0]; if (bitmap0->getComponentFormat() == EBitmask) Log(EError, "Conversions involving bitmasks are currently not supported!"); for (size_t i=1; igetSize() != bitmap0->getSize()) Log(EError, "Bitmap::join(): Detected a size mismatch!"); if (sourceBitmaps[i]->getGamma() != bitmap0->getGamma()) Log(EError, "Bitmap::join(): Detected a gamma mismatch!"); if (sourceBitmaps[i]->getComponentFormat() != bitmap0->getComponentFormat()) Log(EError, "Bitmap::join(): Detected a component format mismatch!"); } /* Determine channel names and metadata */ int sourceChannelCount = 0, channelCount = -1; std::vector channelNames; Properties metadata; for (size_t i=0; igetChannelCount(); const std::vector &names = sourceBitmaps[i]->getChannelNames(); channelNames.insert(channelNames.end(), names.begin(), names.end()); metadata.merge(sourceBitmaps[i]->getMetadata()); } if (!channelNames.empty()) { std::set namesUnique; for (size_t i = 0; i < channelNames.size(); ++i) { if (!channelNames[i].empty()) namesUnique.insert(channelNames[i]); } if (namesUnique.size() != channelNames.size()) Log(EError, "Bitmap::join(): Error -- some of the image supplied color " " channel names, but it was not possible to assign a unique set of names."); } if (fmt == Bitmap::EMultiChannel) channelCount = sourceChannelCount; ref result = new Bitmap(fmt, bitmap0->getComponentFormat(), bitmap0->getSize(), channelCount); channelCount = result->getChannelCount(); if (channelCount != sourceChannelCount) Log(EError, "Bitmap::join(): Error -- supplied the wrong number " "of channels (%i instead of %i)", (int) sourceBitmaps.size(), result->getChannelCount()); result->setMetadata(metadata); result->setGamma(bitmap0->getGamma()); result->setChannelNames(channelNames); size_t pixelCount = (size_t) bitmap0->getSize().x * (size_t) bitmap0->getSize().y; uint8_t **pointers = (uint8_t **) alloca(sourceBitmaps.size() * sizeof(uint8_t *)); size_t *pixelSize = (size_t *) alloca(sourceBitmaps.size() * sizeof(size_t)); for (size_t i = 0; igetUInt8Data(); pixelSize[i] = sourceBitmaps[i]->getBytesPerPixel(); } uint8_t *dest = result->getUInt8Data(); for (size_t i = 0; i Bitmap::crop(const Point2i &offset, const Vector2i &size) const { Assert(offset.x >= 0 && offset.y >= 0 && offset.x + size.x <= m_size.x && offset.y + size.y <= m_size.y); size_t pixelStride = getBytesPerPixel(); size_t sourceStride = pixelStride * m_size.x; size_t targetStride = pixelStride * size.x; ref result = new Bitmap(m_pixelFormat, m_componentFormat, size, m_channelCount); result->setGamma(m_gamma); result->setChannelNames(m_channelNames); result->setMetadata(m_metadata); uint8_t *source = m_data + (offset.x + offset.y * m_size.x) * pixelStride; uint8_t *target = result->getUInt8Data(); for (int y=0; y static void resample(ref rfilter, ReconstructionFilter::EBoundaryCondition bch, ReconstructionFilter::EBoundaryCondition bcv, const Bitmap *source, Bitmap *target, ref temp, Float minValue, Float maxValue, bool filter) { if (!rfilter) { /* Resample using a 2-lobed Lanczos reconstruction filter */ Properties rfilterProps("lanczos"); rfilterProps.setInteger("lobes", 2); ReconstructionFilter *instance = static_cast ( PluginManager::getInstance()->createObject( MTS_CLASS(ReconstructionFilter), rfilterProps)); instance->configure(); rfilter = instance; } if (source->getHeight() == target->getHeight() && source->getWidth() == target->getWidth() && !filter) { memcpy(target->getData(), source->getData(), source->getBufferSize()); return; } int channels = source->getChannelCount(); bool clamp = minValue != -std::numeric_limits::infinity() || maxValue != std::numeric_limits::infinity(); if (source->getWidth() != target->getWidth() || filter) { /* Re-sample along the X direction */ Resampler r(rfilter, bch, source->getWidth(), target->getWidth()); /* Create a bitmap for intermediate storage */ if (!temp) { if (source->getHeight() == target->getHeight() && !filter) temp = target; // write directly to the output bitmap else // otherwise: write to a temporary bitmap temp = new Bitmap(source->getPixelFormat(), source->getComponentFormat(), Vector2i(target->getWidth(), source->getHeight()), channels); } if (clamp) { #if defined(MTS_OPENMP) #pragma omp parallel for #endif for (int y=0; ygetHeight(); ++y) { const Scalar *srcPtr = (Scalar *) source->getUInt8Data() + y * source->getWidth() * channels; Scalar *trgPtr = (Scalar *) temp->getUInt8Data() + y * target->getWidth() * channels; r.resampleAndClamp(srcPtr, 1, trgPtr, 1, channels, safe_cast(minValue), safe_cast(maxValue)); } } else { #if defined(MTS_OPENMP) #pragma omp parallel for #endif for (int y=0; ygetHeight(); ++y) { const Scalar *srcPtr = (Scalar *) source->getUInt8Data() + y * source->getWidth() * channels; Scalar *trgPtr = (Scalar *) temp->getUInt8Data() + y * target->getWidth() * channels; r.resample(srcPtr, 1, trgPtr, 1, channels); } } /* Now, read from the temporary bitmap */ source = temp; } if (source->getHeight() != target->getHeight() || filter) { /* Re-sample along the Y direction */ Resampler r(rfilter, bcv, source->getHeight(), target->getHeight()); if (clamp) { #if defined(MTS_OPENMP) #pragma omp parallel for #endif for (int x=0; xgetWidth(); ++x) { const Scalar *srcPtr = (Scalar *) source->getUInt8Data() + x * channels; Scalar *trgPtr = (Scalar *) target->getUInt8Data() + x * channels; r.resampleAndClamp(srcPtr, source->getWidth(), trgPtr, target->getWidth(), channels, safe_cast(minValue), safe_cast(maxValue)); } } else { #if defined(MTS_OPENMP) #pragma omp parallel for #endif for (int x=0; xgetWidth(); ++x) { const Scalar *srcPtr = (Scalar *) source->getUInt8Data() + x * channels; Scalar *trgPtr = (Scalar *) target->getUInt8Data() + x * channels; r.resample(srcPtr, source->getWidth(), trgPtr, target->getWidth(), channels); } } } } void Bitmap::resample(const ReconstructionFilter *rfilter, ReconstructionFilter::EBoundaryCondition bch, ReconstructionFilter::EBoundaryCondition bcv, Bitmap *target, Bitmap *temp, Float minValue, Float maxValue) const { Assert(getPixelFormat() == target->getPixelFormat() && getComponentFormat() == target->getComponentFormat() && getChannelCount() == target->getChannelCount() && (!temp || temp->getSize() == Vector2i(target->getWidth(), getHeight()))); switch (m_componentFormat) { case EFloat16: mitsuba::resample(rfilter, bch, bcv, this, target, temp, minValue, maxValue, false); break; case EFloat32: mitsuba::resample(rfilter, bch, bcv, this, target, temp, minValue, maxValue, false); break; case EFloat64: mitsuba::resample(rfilter, bch, bcv, this, target, temp, minValue, maxValue, false); break; default: Log(EError, "resample(): Unsupported component type! (must be float16/32/64)"); } } void Bitmap::filter(const ReconstructionFilter *rfilter, ReconstructionFilter::EBoundaryCondition bch, ReconstructionFilter::EBoundaryCondition bcv, Bitmap *target, Bitmap *temp, Float minValue, Float maxValue) const { Assert(getPixelFormat() == target->getPixelFormat() && getComponentFormat() == target->getComponentFormat() && getChannelCount() == target->getChannelCount() && getSize() == target->getSize() && (!temp || temp->getSize() == getSize())); switch (m_componentFormat) { case EFloat16: mitsuba::resample(rfilter, bch, bcv, this, target, temp, minValue, maxValue, true); break; case EFloat32: mitsuba::resample(rfilter, bch, bcv, this, target, temp, minValue, maxValue, true); break; case EFloat64: mitsuba::resample(rfilter, bch, bcv, this, target, temp, minValue, maxValue, true); break; default: Log(EError, "filter(): Unsupported component type! (must be float16/32/64)"); } } ref Bitmap::resample(const ReconstructionFilter *rfilter, ReconstructionFilter::EBoundaryCondition bch, ReconstructionFilter::EBoundaryCondition bcv, const Vector2i &size, Float minValue, Float maxValue) const { ref result = new Bitmap(m_pixelFormat, m_componentFormat, size); result->m_metadata = m_metadata; result->m_gamma = m_gamma; result->m_channelNames = m_channelNames; resample(rfilter, bch, bcv, result, NULL, minValue, maxValue); return result; } ref Bitmap::filter(const ReconstructionFilter *rfilter, ReconstructionFilter::EBoundaryCondition bch, ReconstructionFilter::EBoundaryCondition bcv, Float minValue, Float maxValue) const { ref result = new Bitmap(m_pixelFormat, m_componentFormat, getSize()); result->m_metadata = m_metadata; result->m_gamma = m_gamma; result->m_channelNames = m_channelNames; filter(rfilter, bch, bcv, result, NULL, minValue, maxValue); return result; } bool Bitmap::operator==(const Bitmap &bitmap) const { return m_pixelFormat == bitmap.m_pixelFormat && m_componentFormat == bitmap.m_componentFormat && m_size == bitmap.m_size && m_metadata == bitmap.m_metadata && m_gamma == bitmap.m_gamma && memcmp(bitmap.m_data, m_data, getBufferSize()) == 0; } std::string Bitmap::toString() const { std::ostringstream oss; oss << "Bitmap[" << endl << " type = " << m_pixelFormat << "," << endl << " componentFormat = " << m_componentFormat << "," << endl << " size = " << m_size.toString() << "," << endl; if (isMultiChannel()) oss << " channelCount = " << (int) m_channelCount << "," << endl; if (!m_channelNames.empty()) { oss << " channelNames = [ "; for (size_t i=0; i keys = m_metadata.getPropertyNames(); if (!keys.empty()) { oss << " metadata = {" << endl; for (std::vector::const_iterator it = keys.begin(); it != keys.end(); ) { std::string value = m_metadata.getAsString(*it); if (value.size() > 50) value = value.substr(0, 50) + ".. [truncated]"; oss << " \"" << *it << "\" => \"" << value << "\""; if (++it != keys.end()) oss << ","; oss << endl; } oss << " }," << endl; } oss << " gamma = " << m_gamma << "," << endl << " data = [ " << memString(getBufferSize()) << " of image data ]" << endl << "]"; return oss.str(); } #if defined(MTS_HAS_LIBPNG) void Bitmap::readPNG(Stream *stream) { png_structp png_ptr; png_infop info_ptr; volatile png_bytepp rows = NULL; /* Create buffers */ png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, &png_error_func, &png_warn_func); if (png_ptr == NULL) { Log(EError, "readPNG(): Unable to create PNG data structure"); } info_ptr = png_create_info_struct(png_ptr); if (info_ptr == NULL) { png_destroy_read_struct(&png_ptr, (png_infopp) NULL, (png_infopp) NULL); Log(EError, "readPNG(): Unable to create PNG information structure"); } /* Error handling */ if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL); if (rows) delete[] rows; Log(EError, "readPNG(): Error reading the PNG file!"); } /* Set read helper function */ png_set_read_fn(png_ptr, stream, (png_rw_ptr) png_read_data); int bitDepth, colorType, interlacetype, compressiontype, filtertype; png_read_info(png_ptr, info_ptr); png_uint_32 width = 0, height = 0; png_get_IHDR(png_ptr, info_ptr, &width, &height, &bitDepth, &colorType, &interlacetype, &compressiontype, &filtertype); /* Request various transformations from libpng as necessary */ if (colorType == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png_ptr); // Always expand indexed files else if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth > 1 && bitDepth < 8) png_set_expand_gray_1_2_4_to_8(png_ptr); // Expand 2- and 4-bit grayscale else if (bitDepth == 16 && Stream::getHostByteOrder() == Stream::ELittleEndian) png_set_swap(png_ptr); // Swap the byte order on little endian machines // Expand transparency if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png_ptr); /* Update the information based on the transformations */ png_read_update_info(png_ptr, info_ptr); png_get_IHDR(png_ptr, info_ptr, &width, &height, &bitDepth, &colorType, &interlacetype, &compressiontype, &filtertype); m_size = Vector2i(width, height); switch (colorType) { case PNG_COLOR_TYPE_GRAY: m_pixelFormat = ELuminance; break; case PNG_COLOR_TYPE_GRAY_ALPHA: m_pixelFormat = ELuminanceAlpha; break; case PNG_COLOR_TYPE_RGB: m_pixelFormat = ERGB; break; case PNG_COLOR_TYPE_RGB_ALPHA: m_pixelFormat = ERGBA; break; default: Log(EError, "readPNG(): Unknown color type %i", colorType); break; } updateChannelCount(); switch (bitDepth) { case 1: m_componentFormat = EBitmask; break; case 8: m_componentFormat = EUInt8; break; case 16: m_componentFormat = EUInt16; break; default: Log(EError, "readPNG(): Unsupported bit depth: %i", bitDepth); } /* Load any string-valued metadata */ int textIdx = 0; png_textp text_ptr; png_get_text(png_ptr, info_ptr, &text_ptr, &textIdx); for (int i=0; ikey, text_ptr->text); int intent; double gamma; if (png_get_sRGB(png_ptr, info_ptr, &intent)) { m_gamma = -1; } else if (png_get_gAMA(png_ptr, info_ptr, &gamma)) { m_gamma = (Float) 1 / (Float) gamma; } else { m_gamma = -1; // assume sRGB by default } Log(ETrace, "Loading a %ix%i PNG file", width, height); size_t bufferSize = getBufferSize(); m_data = static_cast(allocAligned(bufferSize)); m_ownsData = true; rows = new png_bytep[m_size.y]; size_t rowBytes = png_get_rowbytes(png_ptr, info_ptr); Assert(rowBytes == getBufferSize() / m_size.y); for (int i=0; i keys = metadata.getPropertyNames(); std::vector values(keys.size()); text = new png_text[keys.size()]; memset(text, 0, sizeof(png_text) * keys.size()); for (size_t i = 0; i(keys[i].c_str()); text[i].text = const_cast(values[i].c_str()); text[i].compression = PNG_TEXT_COMPRESSION_NONE; } png_set_text(png_ptr, info_ptr, text, (int) keys.size()); if (m_gamma == -1) png_set_sRGB_gAMA_and_cHRM(png_ptr, info_ptr, PNG_sRGB_INTENT_ABSOLUTE); else png_set_gAMA(png_ptr, info_ptr, 1 / m_gamma); if (m_componentFormat == EUInt16 && Stream::getHostByteOrder() == Stream::ELittleEndian) png_set_swap(png_ptr); // Swap the byte order on little endian machines png_set_IHDR(png_ptr, info_ptr, m_size.x, m_size.y, bitDepth, colorType, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); png_write_info(png_ptr, info_ptr); rows = new png_bytep[m_size.y]; size_t rowBytes = png_get_rowbytes(png_ptr, info_ptr); Assert(rowBytes == getBufferSize() / m_size.y); for (int i=0; i(allocAligned(getBufferSize())); m_ownsData = true; boost::scoped_array scanlines(new uint8_t*[m_size.y]); for (int i=0; i coverage = Spectrum::getBinCoverage(i); if (!ch_spec[i] && boost::ends_with(name, formatString("%.2f-%.2fnm", coverage.first, coverage.second))) { isSpectralChannel = true; ch_spec[i] = it.name(); break; } } #endif if (!isSpectralChannel) { if (_prefix == "*") multichannel = true; else Log(EWarn, "readOpenEXR(): Don't know what to do with the channel named '%s'", it.name()); } } } bool spectral = true, specialColorProcessing = false, luminanceChromaFormat = false; for (int i=0; i sourceChannels; /* Now, try to categorize this image into some sort of generic class that we know how to deal with */ if (multichannel) { for (Imf::ChannelList::ConstIterator it = channels.begin(); it != channels.end(); ++it) sourceChannels.push_back(it.name()); m_channelCount = (int) sourceChannels.size(); m_pixelFormat = EMultiChannel; formatString = "Multichannel"; } else if (spectral) { m_pixelFormat = ESpectrum; formatString = "Spectrum"; sourceChannels.insert(sourceChannels.begin(), ch_spec, ch_spec + SPECTRUM_SAMPLES); } else if (ch_r && ch_g && ch_b) { m_pixelFormat = ERGB; formatString = "RGB"; sourceChannels.push_back(ch_r); sourceChannels.push_back(ch_g); sourceChannels.push_back(ch_b); } else if (ch_y && ch_by && ch_ry) { /* OpenEXR-specific luminance/chroma format */ m_pixelFormat = ERGB; formatString = "YC"; sourceChannels.push_back(ch_ry); sourceChannels.push_back(ch_y); sourceChannels.push_back(ch_by); luminanceChromaFormat = true; } else if (ch_x && ch_y && ch_z) { m_pixelFormat = EXYZ; formatString = "XYZ"; sourceChannels.push_back(ch_x); sourceChannels.push_back(ch_y); sourceChannels.push_back(ch_z); } else if (ch_y) { m_pixelFormat = ELuminance; formatString = "Luminance"; sourceChannels.push_back(ch_y); } else { Log(EError, "readOpenEXR(): Don't know how to deal with this file! There was " "no known pattern of color/luminance/chroma channels."); } /* Check if there is a chromaticity header entry */ Imf::Chromaticities fileChroma; if (Imf::hasChromaticities(file.header()) && (m_pixelFormat == ERGB || m_pixelFormat == ERGBA)) { fileChroma = Imf::chromaticities(file.header()); Imf::Chromaticities ITURecBT709; Imf::Chromaticities XYZ( Imath::V2f(1.0f, 0.0f), Imath::V2f(0.0f, 1.0f), Imath::V2f(0.0f, 0.0f), Imath::V2f(1.0f/3.0f, 1.0f/3.0f)); if (chromaticitiesMatch(fileChroma, ITURecBT709)) { /* Already in the right space -- do nothing. */ } else if (chromaticitiesMatch(fileChroma, XYZ)) { /* This is an XYZ image */ formatString = "XYZ"; m_pixelFormat = EXYZ; } else { /* Non-standard chromaticities. Special processing is required.. */ specialColorProcessing = true; } } if (ch_a) { m_pixelFormat = (EPixelFormat) (m_pixelFormat | 0x01); sourceChannels.push_back(ch_a); formatString += "/Alpha"; } /* Load metadata if present */ for (Imf::Header::ConstIterator it = header.begin(); it != header.end(); ++it) { std::string name = it.name(), typeName = it.attribute().typeName(); const Imf::StringAttribute *sattr; const Imf::IntAttribute *iattr; const Imf::FloatAttribute *fattr; const Imf::DoubleAttribute *dattr; const Imf::V3fAttribute *vattr; const Imf::M44fAttribute *mattr; if (typeName == "string" && (sattr = header.findTypedAttribute(name.c_str()))) m_metadata.setString(name, sattr->value()); else if (typeName == "int" && (iattr = header.findTypedAttribute(name.c_str()))) m_metadata.setInteger(name, iattr->value()); else if (typeName == "float" && (fattr = header.findTypedAttribute(name.c_str()))) m_metadata.setFloat(name, (Float) fattr->value()); else if (typeName == "double" && (dattr = header.findTypedAttribute(name.c_str()))) m_metadata.setFloat(name, (Float) dattr->value()); else if (typeName == "v3f" && (vattr = header.findTypedAttribute(name.c_str()))) { Imath::V3f vec = vattr->value(); m_metadata.setVector(name, Vector(vec.x, vec.y, vec.z)); } else if (typeName == "m44f" && (mattr = header.findTypedAttribute(name.c_str()))) { Matrix4x4 M; for (int i=0; i<4; ++i) for (int j=0; j<4; ++j) M(i, j) = mattr->value().x[i][j]; m_metadata.setTransform(name, Transform(M)); } } updateChannelCount(); m_gamma = 1.0f; Assert(m_channelCount == (uint8_t) sourceChannels.size()); Imf::PixelType pxType = channels[sourceChannels[0]].type; size_t compSize; std::string encodingString; if (pxType == Imf::HALF) { m_componentFormat = EFloat16; compSize = sizeof(half); encodingString = "float16"; } else if (pxType == Imf::FLOAT) { m_componentFormat = EFloat32; compSize = sizeof(float); encodingString = "float32"; } else if (pxType == Imf::UINT) { m_componentFormat = EUInt32; compSize = sizeof(uint32_t); encodingString = "uint32"; } else { Log(EError, "readOpenEXR(): Invalid component type (must be " "float16, float32, or uint32)"); return; } /* Just how big is this image? */ Imath::Box2i dataWindow = file.header().dataWindow(); m_size = Vector2i(dataWindow.max.x - dataWindow.min.x + 1, dataWindow.max.y - dataWindow.min.y + 1); /* Compute pixel / row strides */ size_t pixelStride = compSize * m_channelCount, rowStride = pixelStride * m_size.x; /* Finally, allocate memory for it */ m_data = static_cast(allocAligned(getBufferSize())); m_ownsData = true; char *ptr = (char *) m_data; ptr -= (dataWindow.min.x + dataWindow.min.y * m_size.x) * pixelStride; ref_vector resampleBuffers((size_t) m_channelCount); ref rfilter; /* Tell OpenEXR where the image data should be put */ Imf::FrameBuffer frameBuffer; for (size_t i=0; igetUInt8Data(); resamplePtr -= (dataWindow.min.x/sampling.x + dataWindow.min.y/sampling.x * channelSize.x) * compSize; frameBuffer.insert(channelName, Imf::Slice(pxType, (char *) resamplePtr, compSize, compSize*channelSize.x, sampling.x, sampling.y)); ptr += compSize; } } Log(EDebug, "Loading a %ix%i OpenEXR file (%s format, %s encoding)", m_size.x, m_size.y, formatString.c_str(), encodingString.c_str()); file.setFrameBuffer(frameBuffer); file.readPixels(dataWindow.min.y, dataWindow.max.y); for (size_t i=0; i ( PluginManager::getInstance()->createObject( MTS_CLASS(ReconstructionFilter), rfilterProps)); rfilter->configure(); } Log(EDebug, "Upsampling layer \"%s\" from %ix%i to %ix%i pixels", sourceChannels[i], resampleBuffers[i]->getWidth(), resampleBuffers[i]->getHeight(), m_size.x, m_size.y); resampleBuffers[i] = resampleBuffers[i]->resample(rfilter, ReconstructionFilter::EClamp, ReconstructionFilter::EClamp, m_size, -std::numeric_limits::max(), std::numeric_limits::max()); size_t pixelCount = (size_t) m_size.x * (size_t) m_size.y; uint8_t *dst = m_data + compSize * i; uint8_t *src = resampleBuffers[i]->getUInt8Data(); for (size_t j=0; j::max(), scale2 = 1.0/scale1; for (size_t j=0; j::max(), scale2 = 1.0/scale1; for (size_t j=0; j keys = metadata.getPropertyNames(); Imf::Header header(m_size.x, m_size.y); for (std::vector::const_iterator it = keys.begin(); it != keys.end(); ++it) { Properties::EPropertyType type = metadata.getType(*it); switch (type) { case Properties::EString: header.insert(it->c_str(), Imf::StringAttribute(metadata.getString(*it))); break; case Properties::EInteger: header.insert(it->c_str(), Imf::IntAttribute(metadata.getInteger(*it))); break; case Properties::EFloat: header.insert(it->c_str(), Imf::FloatAttribute((float) metadata.getFloat(*it))); break; case Properties::EPoint: { Point val = metadata.getPoint(*it); header.insert(it->c_str(), Imf::V3fAttribute( Imath::V3f((float) val.x, (float) val.y, (float) val.z))); } break; case Properties::ETransform: { Matrix4x4 val = metadata.getTransform(*it).getMatrix(); header.insert(it->c_str(), Imf::M44fAttribute(Imath::M44f( (float) val(0, 0), (float) val(0, 1), (float) val(0, 2), (float) val(0, 3), (float) val(1, 0), (float) val(1, 1), (float) val(1, 2), (float) val(1, 3), (float) val(2, 0), (float) val(2, 1), (float) val(2, 2), (float) val(2, 3), (float) val(3, 0), (float) val(3, 1), (float) val(3, 2), (float) val(3, 3)))); } break; default: header.insert(it->c_str(), Imf::StringAttribute(metadata.getAsString(*it))); break; } } if (pixelFormat == EXYZ || pixelFormat == EXYZA) { Imf::addChromaticities(header, Imf::Chromaticities( Imath::V2f(1.0f, 0.0f), Imath::V2f(0.0f, 1.0f), Imath::V2f(0.0f, 0.0f), Imath::V2f(1.0f/3.0f, 1.0f/3.0f))); } else if (pixelFormat == ERGB || pixelFormat == ERGBA) { Imf::addChromaticities(header, Imf::Chromaticities()); } Imf::PixelType compType; size_t compStride; if (m_componentFormat == EFloat16) { compType = Imf::HALF; compStride = 2; } else if (m_componentFormat == EFloat32) { compType = Imf::FLOAT; compStride = 4; } else if (m_componentFormat == EUInt32) { compType = Imf::UINT; compStride = 4; } else { Log(EError, "writeOpenEXR(): Invalid component type (must be " "float16, float32, or uint32)"); return; } if (!m_channelNames.empty() && m_channelNames.size() != (size_t) getChannelCount()) Log(EWarn, "writeOpenEXR(): 'channelNames' has the wrong number of entries (%i, expected %i), ignoring..!", (int) m_channelNames.size(), (int) m_channelCount); bool explicitChannelNames = false; Imf::ChannelList &channels = header.channels(); if (m_channelNames.size() == (size_t) getChannelCount()) { for (size_t i=0; i coverage = Spectrum::getBinCoverage(i); std::string name = formatString("%.2f-%.2fnm", coverage.first, coverage.second); channels.insert(name.c_str(), Imf::Channel(compType)); } } else if (pixelFormat == EMultiChannel) { for (int i=0; i coverage = Spectrum::getBinCoverage(i); std::string name = formatString("%.2f-%.2fnm", coverage.first, coverage.second); frameBuffer.insert(name.c_str(), Imf::Slice(compType, ptr, pixelStride, rowStride)); ptr += compStride; } } else if (pixelFormat == EMultiChannel) { for (int i=0; igetByteOrder(); stream->setByteOrder(Stream::ELittleEndian); int headerSize = stream->readUChar(); if (stream->readUChar() != 0) Log(EError, "readTGA(): indexed files are not supported!"); int colorType = stream->readUChar(); if (colorType != 2 && colorType != 3 && colorType != 10 && colorType != 11) Log(EError, "readTGA(): only grayscale & RGB[A] files are supported!"); stream->skip(9); int width = stream->readShort(); int height = stream->readShort(); uint8_t bpp = stream->readUChar(); uint8_t descriptor = stream->readUChar(); stream->skip(headerSize); m_size = Vector2i(width, height); m_gamma = -1; m_componentFormat = EUInt8; Log(ETrace, "Loading a %ix%i TGA file", m_size.x, m_size.y); bool vflip = !(descriptor & (1 << 5)); bool greyscale = colorType == 3 || colorType == 11; bool rle = colorType & 8; if ((bpp == 8 && !greyscale) || (bpp != 8 && greyscale)) Log(EError, "readTGA(): Invalid bit depth!"); switch (bpp) { case 8: m_pixelFormat = ELuminance; break; case 24: m_pixelFormat = ERGB; break; case 32: m_pixelFormat = ERGBA; break; default: Log(EError, "readTGA(): Invalid bit depth!"); } updateChannelCount(); size_t bufferSize = getBufferSize(), rowSize = bufferSize / height; m_data = static_cast(allocAligned(bufferSize)); m_ownsData = true; int channels = bpp/8; if (!rle) { for (int y=0; yread(m_data + targetY * rowSize, rowSize); } } else { /* Decode an RLE-encoded image */ uint8_t temp[4], *ptr = m_data, *end = m_data + bufferSize; while (ptr != end) { uint8_t value = stream->readUChar(); if (value & 0x80) { /* Run length packet */ uint8_t count = (value & 0x7F) + 1; stream->read(temp, channels); for (uint32_t i=0; ireadUChar(); } } if (vflip) flipVertically(); } if (!greyscale) { /* Convert BGR to RGB */ for (size_t i=0; isetByteOrder(byteOrder); } void Bitmap::readBMP(Stream *stream) { Stream::EByteOrder byteOrder = stream->getByteOrder(); stream->setByteOrder(Stream::ELittleEndian); uint8_t magic1 = stream->readUChar(); uint8_t magic2 = stream->readUChar(); if (magic1 != 'B' || magic2 != 'M') Log(EError, "readBMP(): Invalid header identifier!"); stream->skip(8); uint32_t bmpOffset = stream->readUInt(); uint32_t headerSize = stream->readUInt(); int32_t width = stream->readInt(); int32_t height = stream->readInt(); uint16_t nplanes = stream->readUShort(); uint16_t bpp = stream->readUShort(); uint32_t compressionType = stream->readUInt(); stream->skip(bmpOffset-34); if (headerSize != 40 || nplanes != 1 || width <= 0) Log(EError, "readBMP(): Unsupported BMP format encountered!"); if (compressionType != 0) Log(EError, "readBMP(): Compressed files are currently not supported!"); m_size = Vector2i(width, std::abs(height)); m_componentFormat = EUInt8; m_gamma = -1.0f; switch (bpp) { case 1: m_pixelFormat = ELuminance; m_componentFormat = EBitmask; break; case 8: m_pixelFormat = ELuminance; break; case 16: m_pixelFormat = ELuminanceAlpha; break; case 24: m_pixelFormat = ERGB; break; case 32: m_pixelFormat = ERGBA; break; default: Log(EError, "readBMP(): Invalid bit depth (%i)!", bpp); } updateChannelCount(); size_t bufferSize = getBufferSize(); m_data = static_cast(allocAligned(bufferSize)); m_ownsData = true; Log(ETrace, "Loading a %ix%i BMP file", m_size.x, m_size.y); int rowSize = (int) bufferSize / m_size.y; int padding = -rowSize & 3; bool vflip = height > 0; for (int y=0; yread(m_data + rowSize * targetY, rowSize); stream->skip(padding); } if (m_pixelFormat == ERGB || m_pixelFormat == ERGBA) { int channels = getChannelCount(); for (size_t i=0; isetByteOrder(byteOrder); } /* The following is based on code by Bruce Walter */ namespace detail { static inline void RGBE_FromFloat(float *data, uint8_t rgbe[4]) { /* Find the largest contribution */ Float max = std::max(std::max(data[0], data[1]), data[2]); if (max < 1e-32) { rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; } else { int e; /* Extract exponent and convert the fractional part into the [0..255] range. Afterwards, divide by max so that any color component multiplied by the result will be in [0,255] */ max = std::frexp(max, &e) * (Float) 256 / max; rgbe[0] = (uint8_t) (data[0] * max); rgbe[1] = (uint8_t) (data[1] * max); rgbe[2] = (uint8_t) (data[2] * max); rgbe[3] = e+128; /* Exponent value in bias format */ } } static inline void RGBE_ToFloat(uint8_t rgbe[4], float *data) { if (rgbe[3]) { /* nonzero pixel */ float f = std::ldexp(1.0f, (int) rgbe[3] - (128+8)); for (int i=0; i<3; ++i) data[i] = rgbe[i] * f; } else { memset(data, 0, sizeof(float)*3); } } /* The code below is only needed for the run-length encoded files. Run length encoding adds considerable complexity but does save some space. For each scanline, each channel (r,g,b,e) is encoded separately for better compression. */ static inline void RGBE_WriteBytes_RLE(Stream *stream, uint8_t *data, int numbytes) { int cur = 0; uint8_t buf[2]; while (cur < numbytes) { int beg_run = cur; /* find next run of length at least 4 if one exists */ int run_count = 0, old_run_count = 0; while (run_count < 4 && beg_run < numbytes) { beg_run += run_count; old_run_count = run_count; run_count = 1; while( (beg_run + run_count < numbytes) && (run_count < 127) && (data[beg_run] == data[beg_run + run_count])) run_count++; } /* if data before next big run is a short run then write it as such */ if (old_run_count > 1 && old_run_count == beg_run - cur) { buf[0] = 128 + old_run_count; /*write short run*/ buf[1] = data[cur]; stream->write(buf, 2); cur = beg_run; } /* write out bytes until we reach the start of the next run */ while (cur < beg_run) { int nonrun_count = beg_run - cur; if (nonrun_count > 128) nonrun_count = 128; buf[0] = nonrun_count; stream->write(buf, 1); stream->write(&data[cur], nonrun_count); cur += nonrun_count; } /* write out next run if one was found */ if (run_count >= 4) { buf[0] = 128 + run_count; buf[1] = data[beg_run]; stream->write(buf, 2); cur += run_count; } } } /* simple read routine. will not correctly handle run length encoding */ inline static void RGBE_ReadPixels(Stream *stream, float *data, size_t numpixels) { while (numpixels-- > 0) { uint8_t rgbe[4]; stream->read(rgbe, 4); RGBE_ToFloat(rgbe, data); data += 3; } } } void Bitmap::readRGBE(Stream *stream) { std::string line = stream->readLine(); if (line.length() < 2 || line[0] != '#' || line[1] != '?') Log(EError, "readRGBE(): Invalid header!"); bool format_recognized = false; while (true) { line = stream->readLine(); if (boost::starts_with(line, "FORMAT=32-bit_rle_rgbe")) format_recognized = true; if (boost::starts_with(line, "-Y ")) { if (sscanf(line.c_str(), "-Y %i +X %i", &m_size.y, &m_size.x) < 2) Log(EError, "readRGBE(): parser error!"); break; } } if (!format_recognized) Log(EError, "readRGBE(): invalid format!"); m_pixelFormat = ERGB; m_componentFormat = EFloat32; m_channelCount = 3; m_gamma = 1.0f; m_data = static_cast(allocAligned(getBufferSize())); m_ownsData = true; float *data = (float *) m_data; if (m_size.x < 8 || m_size.x > 0x7fff) { /* run length encoding is not allowed so read flat*/ detail::RGBE_ReadPixels(stream, data, (size_t) m_size.x * (size_t) m_size.y); return; } uint8_t *buffer = new uint8_t[4*m_size.x]; try { /* Read in each successive scanline */ for (int y=0; yread(rgbe, 4); if (rgbe[0] != 2 || rgbe[1] != 2 || rgbe[2] & 0x80) { /* this file is not run length encoded */ detail::RGBE_ToFloat(rgbe, data); detail::RGBE_ReadPixels(stream, data + 3, (size_t) m_size.x * (size_t) m_size.y - 1); return; } if ((((int) rgbe[2]) << 8 | rgbe[3]) != m_size.x) Log(EError, "readRGBE(): wrong scanline width!"); uint8_t *ptr = buffer; /* read each of the four channels for the scanline into the buffer */ for (int i=0;i<4;i++) { uint8_t *ptr_end = buffer + (i+1) * m_size.x; while (ptr < ptr_end) { uint8_t buf[2]; stream->read(buf, 2); if (buf[0] > 128) { /* a run of the same value */ int count = buf[0] - 128; if (count == 0 || count > ptr_end - ptr) Log(EError, "readRGBE(): bad scanline data!"); while (count-- > 0) *ptr++ = buf[1]; } else { /* a non-run */ int count = buf[0]; if (count == 0 || count > ptr_end - ptr) Log(EError, "readRGBE(): bad scanline data!"); *ptr++ = buf[1]; if (--count > 0) stream->read(ptr, count); ptr += count; } } } /* now convert data from buffer into floats */ for (int i=0; iwriteLine("#?RGBE"); std::vector keys = m_metadata.getPropertyNames(); for (std::vector::const_iterator it = keys.begin(); it != keys.end(); ++it) { stream->writeLine(formatString("# Metadata [%s]:", it->c_str())); std::istringstream iss(m_metadata.getAsString(*it)); std::string buf; while (std::getline(iss, buf)) stream->writeLine(formatString("# %s", buf.c_str())); } stream->writeLine("FORMAT=32-bit_rle_rgbe\n"); stream->writeLine(formatString("-Y %i +X %i", m_size.y, m_size.x)); float *data = (float *) m_data; if (m_size.x < 8 || m_size.x > 0x7fff) { /* Run length encoding is not allowed so write flat*/ uint8_t rgbe[4]; for (size_t i=0; i<(size_t) m_size.x * (size_t) m_size.y; ++i) { detail::RGBE_FromFloat(data, rgbe); data += (m_pixelFormat == ERGB) ? 3 : 4; stream->write(rgbe, 4); } return; } uint8_t *buffer = new uint8_t[4*m_size.x]; for (int y=0; y> 8), (uint8_t) (m_size.x & 0xFF) }; stream->write(rgbe, 4); for (int x=0; xreadChar(); if (::isspace(data)) break; result += data; } return result; } void Bitmap::readPFM(Stream *stream) { char header[3]; stream->read(header, 3); if (header[0] != 'P' || !(header[1] == 'F' || header[1] == 'f')) Log(EError, "readPFM(): Invalid header!"); bool color = (header[1] == 'F'); m_pixelFormat = color ? ERGB : ELuminance; m_componentFormat = EFloat32; m_channelCount = color ? 3 : 1; m_gamma = 1.0f; char *end_ptr = NULL; std::string widthString = pfmReadString(stream); m_size.x = (int) strtol(widthString.c_str(), &end_ptr, 10); if (*end_ptr != '\0') SLog(EError, "Could not parse image dimensions!"); std::string heightString = pfmReadString(stream); m_size.y = (int) strtol(heightString.c_str(), &end_ptr, 10); if (*end_ptr != '\0') SLog(EError, "Could not parse image dimensions!"); std::string scaleAndOrderString = pfmReadString(stream); float scaleAndOrder = (float) strtod(scaleAndOrderString.c_str(), &end_ptr); if (*end_ptr != '\0') SLog(EError, "Could not parse scale/order information!"); m_data = static_cast(allocAligned(getBufferSize())); m_ownsData = true; float *data = (float *) m_data; Stream::EByteOrder backup = stream->getByteOrder(); size_t size = getPixelCount() * m_channelCount; stream->setByteOrder(scaleAndOrder <= 0.0f ? Stream::ELittleEndian : Stream::EBigEndian); try { stream->readSingleArray(data, size); } catch (...) { stream->setByteOrder(backup); throw; } stream->setByteOrder(backup); const float scale = std::abs(scaleAndOrder); if (scale != 1) { for (size_t i=0; iwrite(header.c_str(), header.length()); float *data = (float *) m_data; if (m_pixelFormat == ERGB || m_pixelFormat == ELuminance) { size_t scanline = (size_t) m_size.x * m_channelCount; for (int y=0; ywrite(data + scanline*(m_size.y - 1 - y), scanline * sizeof(float)); } else { /* For convenience: also handle images with an alpha channel, but strip it out while saving the data */ size_t scanline = (size_t) m_size.x * m_channelCount; float *temp = (float *) alloca(scanline * sizeof(float)); for (int y=0; ywrite(temp, sizeof(float) * m_size.x * (m_channelCount-1)); } } } void Bitmap::readPPM(Stream *stream) { int field = 0, nChars = 0; std::string fields[4]; while (field < 4) { char c = stream->readChar(); if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { if (nChars != 0) { nChars = 0; ++field; } } else { fields[field] += c; ++nChars; } } if (fields[0] != "P6") Log(EError, "readPPM(): invalid format!"); int intValues[3]; for (int i=0; i<3; ++i) { char *end_ptr = NULL; intValues[i] = strtol(fields[i+1].c_str(), &end_ptr, 10); if (*end_ptr != '\0') SLog(EError, "readPPM(): unable to parse the file header!"); } m_size.x = intValues[0]; m_size.y = intValues[1]; m_pixelFormat = ERGB; m_channelCount = 3; m_gamma = -1.0f; m_ownsData = true; m_componentFormat = intValues[2] <= 0xFF ? EUInt8 : EUInt16; size_t size = getBufferSize(); m_data = static_cast(allocAligned(size)); stream->read(m_data, size); } void Bitmap::writePPM(Stream *stream) const { if (m_pixelFormat != ERGB || (m_componentFormat != EUInt8 && m_componentFormat != EUInt16)) Log(EError, "writePPM(): Only 8 or 16-bit RGB images are supported"); stream->writeLine(formatString("P6\n%i\n%i\n%i\n", m_size.x, m_size.y, m_componentFormat == EUInt8 ? 0xFF : 0xFFFF).c_str()); stream->write(m_data, getBufferSize()); } void Bitmap::staticInitialization() { #if defined(MTS_HAS_OPENEXR) /* Prevent races during the OpenEXR initialization */ Imf::staticInitialize(); /* Use multiple threads to read/write OpenEXR files */ Imf::setGlobalThreadCount(getCoreCount()); #endif /* Initialize the Bitmap format conversion */ FormatConverter::staticInitialization(); #if defined(MTS_HAS_FFTW) /* Initialize FFTW if enabled */ fftw_init_threads(); fftw_plan_with_nthreads(getCoreCount()); #endif } void Bitmap::staticShutdown() { FormatConverter::staticShutdown(); #if defined(MTS_HAS_FFTW) fftw_cleanup_threads(); #endif } ref Bitmap::expand() { if (m_componentFormat != EBitmask) return this; ref output= new Bitmap(m_pixelFormat, EUInt8, m_size); uint8_t *outputBuffer = output->getUInt8Data(); size_t bytesPerRow = (m_size.x * m_channelCount + 7) / 8; // round up to full bytes for (int y=0; y