From 9f9df009ef1a73b012dfe5365e9c9b9ba24218d9 Mon Sep 17 00:00:00 2001
From: Wenzel Jakob <wenzel@inf.ethz.ch>
Date: Wed, 12 Nov 2014 15:55:17 +0100
Subject: [PATCH] mtsgui: ability to view multi-channel EXRs

---
 include/mitsuba/core/bitmap.h | 35 +++++++++++++-
 src/libcore/bitmap.cpp        | 91 +++++++++++++++++++----------------
 src/libhw/glrenderer.cpp      |  3 +-
 src/librender/trimesh.cpp     |  4 +-
 src/mtsgui/common.h           |  3 ++
 src/mtsgui/glwidget.cpp       | 54 ++++++++++++++++++---
 src/mtsgui/mainwindow.cpp     |  8 ++-
 src/mtsgui/sceneloader.cpp    | 15 +++++-
 8 files changed, 159 insertions(+), 54 deletions(-)

diff --git a/include/mitsuba/core/bitmap.h b/include/mitsuba/core/bitmap.h
index 84227114..02cf8fd2 100644
--- a/include/mitsuba/core/bitmap.h
+++ b/include/mitsuba/core/bitmap.h
@@ -297,6 +297,22 @@ public:
 		EDivision
 	};
 
+	/**
+	 * \brief Describes a sub-layer of a multilayer bitmap (e.g. OpenEXR)
+	 *
+	 * A layer is defined as a named collection of bitmap channels and a pixel format
+	 *
+	 * This data structure is used by \ref Bitmap::getLayers()
+	 */
+	struct Layer {
+		/// Descriptive name of the bitmap layer
+		std::string name;
+		/// Pixel format that should be associated with underlying combination of bitmap channels
+		EPixelFormat format;
+		/// References to bitmap channels
+		std::vector<int> channels;
+	};
+
 	/**
 	 * \brief Create a bitmap of the specified type and allocate
 	 * the necessary amount of memory
@@ -813,9 +829,26 @@ public:
 	 */
 	ref<Bitmap> extractChannels(Bitmap::EPixelFormat fmt, const std::vector<int> &channels) const;
 
+	/**
+	 * \brief Extracts layer information from the bitmap
+	 *
+	 * This is a convenience function which analyzes the bitmap's
+	 * channel names and extracts groups matching a name
+	 * convention specified by the OpenEXR standard.
+	 *
+	 * A series of channel names referred to as
+	 *  'diffuse.R', 'diffuse.B', and 'diffuse.G'
+	 * will be identified as a single \ref Bitmap::ERGB "layer".
+	 * This works for RGB[A]/XYZ[A], and Luminance[A] images
+	 */
+	std::vector<Layer> getLayers() const;
+
 	/**
 	 * \brief Split an multi-channel image buffer (e.g. from an OpenEXR image
-	 * with lots of AOVs) into group of RGB[A]/XYZ[A]/Luminance images
+	 * with lots of AOVs) into its constituent layers
+	 *
+	 * This operation internally calls \ref getLayers() to extract bitmap
+	 * layers followed by one or more calls to \ref extractChannels()
 	 */
 	std::map<std::string, Bitmap *> split() const;
 
diff --git a/src/libcore/bitmap.cpp b/src/libcore/bitmap.cpp
index 5261e75f..f26c582b 100644
--- a/src/libcore/bitmap.cpp
+++ b/src/libcore/bitmap.cpp
@@ -1875,32 +1875,34 @@ void Bitmap::tonemapReinhard(Float &logAvgLuminance, Float &maxLuminance, Float
 	}
 }
 
-
-std::map<std::string, Bitmap *> Bitmap::split() const {
+std::vector<Bitmap::Layer> Bitmap::getLayers() const {
 	typedef std::map<std::string, int> ChannelMap;
-	std::map<std::string, Bitmap *> result;
 	if (m_channelNames.empty())
-		Log(EError, "Bitmap::split(): color channel names not available!");
+		Log(EError, "Bitmap::getLayers(): required color channel names were not available!");
 
 	ChannelMap channels;
 	for (size_t i=0; i<m_channelNames.size(); ++i)
 		channels[boost::to_lower_copy(m_channelNames[i])] = (int) i;
 
+	std::vector<Layer> layers;
 	for (size_t i=0; i<m_channelNames.size(); ++i) {
 		std::string name = boost::to_lower_copy(m_channelNames[i]);
 		if (channels.find(name) == channels.end())
 			continue;
-		std::string prefix = "";
+		std::string prefix = name;
 		char postfix = '\0';
+		Layer layer;
 
-		if (!name.empty()) {
-			prefix = name.substr(0, name.length()-1);
-			postfix = name[name.length()-1];
+		layer.name = m_channelNames[i];
+		if (name.length() == 1) {
+			prefix = layer.name = "";
+			postfix = name[name.length() - 1];
+		} if (name.length() >= 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];
 		}
 
-		std::vector<int> extractChannels;
-		EPixelFormat extractFormat;
-
 		ChannelMap::iterator
 			itR = channels.find(prefix + "r"),
 			itG = channels.find(prefix + "g"),
@@ -1915,68 +1917,75 @@ std::map<std::string, Bitmap *> Bitmap::split() const {
 		bool maybeY = postfix == 'y' || postfix == 'a';
 
 		if (maybeRGB && itR != channels.end() && itG != channels.end() && itB != channels.end()) {
-			extractFormat = ERGB;
-			extractChannels.push_back(itR->second);
-			extractChannels.push_back(itG->second);
-			extractChannels.push_back(itB->second);
+			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()) {
-				extractFormat = ERGBA;
-				extractChannels.push_back(itA->second);
+				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()) {
-			extractFormat = EXYZ;
-			extractChannels.push_back(itX->second);
-			extractChannels.push_back(itY->second);
-			extractChannels.push_back(itZ->second);
+			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()) {
-				extractFormat = EXYZA;
-				extractChannels.push_back(itA->second);
+				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()) {
-			extractFormat = ELuminance;
-			extractChannels.push_back(itY->second);
+			layer.format = ELuminance;
+			layer.channels.push_back(itY->second);
 			if (itA != channels.end()) {
-				extractFormat = ELuminanceAlpha;
-				extractChannels.push_back(itA->second);
+				layer.format = ELuminanceAlpha;
+				layer.channels.push_back(itA->second);
 				channels.erase(prefix + "a");
 			}
 			channels.erase(prefix + "y");
 		} else {
-			extractFormat = ELuminance;
-			extractChannels.push_back((int) i);
+			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<std::string, Bitmap *> Bitmap::split() const {
+	std::map<std::string, Bitmap *> result;
+
+	std::vector<Layer> layers = getLayers();
+	for (size_t i=0; i<layers.size(); ++i) {
+		const Layer &layer = layers[i];
 		std::vector<std::string> channelNames;
-		for (size_t j=0; j<extractChannels.size(); ++j)
-			channelNames.push_back(m_channelNames[extractChannels[j]]);
+		for (size_t j=0; j<layer.channels.size(); ++j)
+			channelNames.push_back(m_channelNames[layer.channels[j]]);
 
 		Bitmap *bitmap = NULL;
 		{
-			ref<Bitmap> _bitmap = this->extractChannels(extractFormat, extractChannels);
+			ref<Bitmap> _bitmap = this->extractChannels(layer.format, layer.channels);
 			bitmap = _bitmap.get();
 			bitmap->incRef();
 		}
 		bitmap->decRef(false);
 		bitmap->setChannelNames(channelNames);
 
-		prefix = m_channelNames[i];
-		if (!prefix.empty())
-			prefix = prefix.substr(0, prefix.length()-1);
-		if (!prefix.empty() && prefix[prefix.length()-1] == '.')
-			prefix = prefix.substr(0, prefix.length()-1);
-
-		if (result.find(prefix) != result.end())
-			Log(EError, "Internal error -- encountered two sub-images with the same prefix");
-		result[prefix] = bitmap;
+		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;
 }
diff --git a/src/libhw/glrenderer.cpp b/src/libhw/glrenderer.cpp
index e65e400f..7ad86a35 100644
--- a/src/libhw/glrenderer.cpp
+++ b/src/libhw/glrenderer.cpp
@@ -771,7 +771,8 @@ void GLRenderer::drawText(const Point2i &_pos,
 	glLoadIdentity();
 	font->getTexture()->bind();
 	glEnable(GL_BLEND);
-	glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ONE_MINUS_SRC_COLOR);
+	glBlendFunc(GL_SRC_ALPHA, GL_ONE);
+//	glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ONE_MINUS_SRC_COLOR);
 	Point2i pos(_pos);
 	int initial = pos.x;
 
diff --git a/src/librender/trimesh.cpp b/src/librender/trimesh.cpp
index e28ba68e..8326fc5d 100644
--- a/src/librender/trimesh.cpp
+++ b/src/librender/trimesh.cpp
@@ -1110,7 +1110,7 @@ void TriMesh::writePLY(const fs::path &path) const {
 			*ptr += (uint8_t) std::max(0.0f, std::min(255.0f, (float) m_colors[i][2] * 255.0f + 0.5f));
 		}
 	}
-	Assert(ptr-vertexStorage == vertexStorageSize);
+	Assert((size_t) (ptr-vertexStorage) == vertexStorageSize);
 	os.write((const char *) vertexStorage, vertexStorageSize);
 	delete[] vertexStorage;
 
@@ -1122,7 +1122,7 @@ void TriMesh::writePLY(const fs::path &path) const {
 		memcpy(ptr, &m_triangles[i], sizeof(Triangle));
 		ptr += sizeof(Triangle);
 	}
-	Assert(ptr-faceStorage == faceStorageSize);
+	Assert((size_t) (ptr-faceStorage) == faceStorageSize);
 	os.write((const char *) faceStorage, faceStorageSize);
 	delete[] faceStorage;
 
diff --git a/src/mtsgui/common.h b/src/mtsgui/common.h
index 5f3bf675..47d7371e 100644
--- a/src/mtsgui/common.h
+++ b/src/mtsgui/common.h
@@ -184,7 +184,9 @@ struct SceneContext {
 	float progress;
 	QString eta, progressName;
 	ref<Bitmap> framebuffer;
+	std::vector<std::pair<std::string, Bitmap*> > layers;
 	std::set<VisualWorkUnit, block_comparator> workUnits;
+	int currentLayer;
 	EMode mode, cancelMode;
 	Float gamma, exposure, clamping;
 	bool srgb;
@@ -209,6 +211,7 @@ struct SceneContext {
 
 	SceneContext() : scene(NULL), sceneResID(-1),
 		renderJob(NULL), wasRendering(false),
+		currentLayer(0),
 		selectionMode(ENothing),
 		selectedShape(NULL) { }
 
diff --git a/src/mtsgui/glwidget.cpp b/src/mtsgui/glwidget.cpp
index 3ecdf0d6..8f2eb9ab 100644
--- a/src/mtsgui/glwidget.cpp
+++ b/src/mtsgui/glwidget.cpp
@@ -220,6 +220,14 @@ void GLWidget::setScene(SceneContext *context) {
 		m_movementTimer->stop();
 	m_context = context;
 
+	if (context && context->layers.size() > 0) {
+		m_statusMessage =
+			formatString("Showing layer \"%s\" (%i/%i); use '[' and ']' to switch.",
+				m_context->layers[m_context->currentLayer].first.c_str(),
+				m_context->currentLayer+1, m_context->layers.size());
+		m_statusTimer->reset();
+	}
+
 	if (context && context->scene == NULL)
 		context = NULL;
 
@@ -503,7 +511,9 @@ void GLWidget::keyPressEvent(QKeyEvent *event) {
 		emit stopRendering();
 	}
 
-	if (event->isAutoRepeat() || !m_context)
+	if (!m_context ||
+		(event->isAutoRepeat() && event->key() != Qt::Key_BracketLeft &&
+		 event->key() != Qt::Key_BracketRight))
 		return;
 
 	switch (event->key()) {
@@ -526,7 +536,15 @@ void GLWidget::keyPressEvent(QKeyEvent *event) {
 		case Qt::Key_Down:
 			m_downKeyDown = true; break;
 		case Qt::Key_BracketLeft:
-			if (m_context->showKDTree) {
+			if (m_context->layers.size() > 0) {
+				m_context->currentLayer = std::max(0, m_context->currentLayer-1);
+				m_context->framebuffer = m_context->layers[m_context->currentLayer].second->convert(Bitmap::ERGBA, Bitmap::EFloat32);
+				m_statusMessage =
+					formatString("Showing layer \"%s\" (%i/%i); use '[' and ']' to switch.",
+						m_context->layers[m_context->currentLayer].first.c_str(),
+						m_context->currentLayer+1, m_context->layers.size());
+				m_statusTimer->reset();
+			} else if (m_context->showKDTree) {
 				m_context->shownKDTreeLevel
 					= std::max(0, m_context->shownKDTreeLevel - 1);
 				updateGL();
@@ -534,7 +552,15 @@ void GLWidget::keyPressEvent(QKeyEvent *event) {
 			}
 			break;
 		case Qt::Key_BracketRight:
-			if (m_context->showKDTree) {
+			if (m_context->layers.size() > 0) {
+				m_context->currentLayer = std::min((int) m_context->layers.size() - 1, m_context->currentLayer+1);
+				m_context->framebuffer = m_context->layers[m_context->currentLayer].second->convert(Bitmap::ERGBA, Bitmap::EFloat32);
+				m_statusMessage =
+					formatString("Showing layer \"%s\" (%i/%i); use '[' and ']' to switch.",
+						m_context->layers[m_context->currentLayer].first.c_str(),
+						m_context->currentLayer+1, m_context->layers.size());
+				m_statusTimer->reset();
+			} else if (m_context->showKDTree) {
 				m_context->shownKDTreeLevel++;
 				updateGL();
 				return;
@@ -1215,11 +1241,17 @@ void GLWidget::paintGL() {
 		if (m_context->mode == EPreview) {
 			m_preview->releaseBuffer(entry);
 			if (m_context->showKDTree) {
+				std::string message = "kd-tree visualization mode\nPress '[' "
+					"and ']' to change the shown level";
+				Vector2i size = m_font->getSize(message);
 				int pos = m_statusMessage.empty() ? 10 : 30;
+				m_renderer->setBlendMode(Renderer::EBlendAlpha);
+				m_renderer->setColor(Spectrum(0.0f), 0.5f);
+				m_renderer->drawFilledRectangle(Point2i(5, pos-2), Point2i(15, pos+5)+size);
+				m_renderer->setBlendMode(Renderer::EBlendNone);
 				m_renderer->setColor(Spectrum(1.0f));
-				m_renderer->drawText(Point2i(10, pos), m_font,
-					formatString("kd-tree visualization mode\nPress '[' "
-					"and ']' to change the shown level"));
+				m_renderer->setColor(Spectrum(1.0f));
+				m_renderer->drawText(Point2i(10, pos), m_font, message);
 			}
 		}
 
@@ -1244,7 +1276,15 @@ void GLWidget::paintGL() {
 				m_statusMessage = "Camera type is incompatible with the OpenGL preview!";
 		}
 
-		if (!m_statusMessage.empty() && m_context->mode == EPreview) {
+		if (!m_statusMessage.empty() && (m_context->mode == EPreview
+			|| (m_context->mode == ERender && m_context->scene == NULL))) {
+			m_renderer->setDepthTest(false);
+			Vector2i size = m_font->getSize(m_statusMessage);
+			m_renderer->setBlendMode(Renderer::EBlendAlpha);
+			m_renderer->setColor(Spectrum(0.0f), 0.5f);
+			m_renderer->drawFilledRectangle(Point2i(5, 7), Point2i(15, 15)+size);
+			m_renderer->setBlendMode(Renderer::EBlendNone);
+			m_renderer->setColor(Spectrum(1.0f));
 			m_renderer->drawText(Point2i(10, 10), m_font, m_statusMessage);
 			if (m_statusTimer->getMilliseconds() > 2000)
 				m_statusMessage = "";
diff --git a/src/mtsgui/mainwindow.cpp b/src/mtsgui/mainwindow.cpp
index afcc796f..68bafac6 100644
--- a/src/mtsgui/mainwindow.cpp
+++ b/src/mtsgui/mainwindow.cpp
@@ -398,7 +398,7 @@ bool MainWindow::initWorkersProcessArgv() {
 		scheduler->registerWorker(new LocalWorker(-1, formatString("wrk%i", 0), m_workerPriority));
 	}
 
-	for (int i=0; i<toBeLoaded.size(); ++i)
+	for (size_t i=0; i<toBeLoaded.size(); ++i)
 		loadFile(toBeLoaded[i].first, toBeLoaded[i].second);
 
 	scheduler->start();
@@ -2157,6 +2157,10 @@ SceneContext::SceneContext(SceneContext *ctx) {
 	selectionMode = ctx->selectionMode;
 	originalSize = ctx->originalSize;
 	doc = ctx->doc.cloneNode(true).toDocument();
+	layers = ctx->layers;
+	for (size_t i=0; i<layers.size(); ++i)
+		layers[i].second->incRef();
+	currentLayer = ctx->currentLayer;
 }
 
 SceneContext::~SceneContext() {
@@ -2166,6 +2170,8 @@ SceneContext::~SceneContext() {
 		previewBuffer.buffer->decRef();
 	if (previewBuffer.sync)
 		previewBuffer.sync->decRef();
+	for (size_t i=0; i<layers.size(); ++i)
+		layers[i].second->decRef();
 }
 
 int SceneContext::detectPathLength() const {
diff --git a/src/mtsgui/sceneloader.cpp b/src/mtsgui/sceneloader.cpp
index 38ae555b..c275028c 100644
--- a/src/mtsgui/sceneloader.cpp
+++ b/src/mtsgui/sceneloader.cpp
@@ -65,12 +65,25 @@ void SceneLoader::run() {
 		m_result->toneMappingMethod = (EToneMappingMethod) settings.value("preview_toneMappingMethod", EGamma).toInt();
 		m_result->diffuseSources = settings.value("preview_diffuseSources", true).toBool();
 		m_result->diffuseReceivers = settings.value("preview_diffuseReceivers", false).toBool();
+		m_result->currentLayer = 0;
 
 		if (suffix == "exr" || suffix == "png"  || suffix == "jpg" || suffix == "jpeg" ||
 		    suffix == "hdr" || suffix == "rgbe" || suffix == "pfm" || suffix == "ppm") {
 			/* This is an image, not a scene */
 			ref<FileStream> fs = new FileStream(toFsPath(m_filename), FileStream::EReadOnly);
-			ref<Bitmap> bitmap = new Bitmap(Bitmap::EAuto, fs);
+			ref<Bitmap> bitmap = new Bitmap(Bitmap::EAuto, fs, (suffix == "exr") ? "*" : "");
+
+			if (suffix == "exr" && bitmap->getLayers().size() > 1) {
+				std::map<std::string, Bitmap*> bitmaps = bitmap->split();
+				for (std::map<std::string, Bitmap *>::iterator it = bitmaps.begin(); it != bitmaps.end(); ++it) {
+					std::string name = it->first;
+					Bitmap *layer = it->second;
+					layer->incRef();
+					m_result->layers.push_back(std::make_pair(name, layer));
+				}
+				bitmap = m_result->layers[m_result->currentLayer].second;
+			}
+
 			bitmap = bitmap->convert(Bitmap::ERGBA, Bitmap::EFloat32);
 
 			m_result->mode = ERender;