Writing an Imagepipeline Plugin

From LuxCoreRender Wiki
Jump to: navigation, search

This tutorial explains how to add a new plugin to the imagepipeline.
A plugin receives the output of the previous plugin as input and operates on these pixels in screen space, outputting them to the next plugin.

Imagepipeline plugins are written in C++ and optionally in OpenCL. For now, this tutorial contains a C++ only example.

C++ Code

Files that need to be created:

  • Header: include/slg/film/imagepipeline/plugins/yourpluginname.h
  • Source: src/slg/film/imagepipeline/plugins/yourpluginname.cpp

Files that need to be edited:

  • Filmparse: src/slg/film/filmparse.cpp
  • CMakeLists.txt: src/slg/CMakeLists.txt

Example: OverExposureDetection Plugin

As an exmaple for this tutorial, we'll write a plugin that sets the pixel color to red when it detects pixels which are brighter than a specified threshold. The threshold can be specified by the user in the .cfg file.

Header File

Create a new header file with filepath include/slg/film/imagepipeline/plugins/overexposuredetection.h

/***************************************************************************
 * Copyright 1998-2017 by authors (see AUTHORS.txt)                        *
 *                                                                         *
 *   This file is part of LuxRender.                                       *
 *                                                                         *
 * Licensed under the Apache License, Version 2.0 (the "License");         *
 * you may not use this file except in compliance with the License.        *
 * You may obtain a copy of the License at                                 *
 *                                                                         *
 *     http://www.apache.org/licenses/LICENSE-2.0                          *
 *                                                                         *
 * Unless required by applicable law or agreed to in writing, software     *
 * distributed under the License is distributed on an "AS IS" BASIS,       *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.*
 * See the License for the specific language governing permissions and     *
 * limitations under the License.                                          *
 ***************************************************************************/

#ifndef _SLG_OVEREXPOSUREDETECTION_H
#define	_SLG_OVEREXPOSUREDETECTION_H

#include <vector>
#include <memory>
#include <typeinfo>
#include <boost/serialization/version.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/vector.hpp>

#include "eos/portable_oarchive.hpp"
#include "eos/portable_iarchive.hpp"

#include "luxrays/luxrays.h"
#include "luxrays/core/color/color.h"
#include "luxrays/core/oclintersectiondevice.h"
#include "slg/film/imagepipeline/imagepipeline.h"

namespace slg {

//------------------------------------------------------------------------------
// OverExposureDetection plugin
//------------------------------------------------------------------------------

class OverExposureDetection : public ImagePipelinePlugin {
public:
	OverExposureDetection(float threshold);
	virtual ~OverExposureDetection() {};

	virtual ImagePipelinePlugin *Copy() const;

	virtual void Apply(Film &film, const u_int index);

	friend class boost::serialization::access;

private:
	OverExposureDetection();

	template<class Archive> void serialize(Archive &ar, const u_int version) {
		ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ImagePipelinePlugin);
		// TODO: implement serialization for ImageMap
		throw std::runtime_error("OverExposureDetection serialization not yet supported");
	}

	float threshold;
};

}

BOOST_CLASS_VERSION(slg::OverExposureDetection, 1)

BOOST_CLASS_EXPORT_KEY(slg::OverExposureDetection)

#endif	/* _SLG_OVEREXPOSUREDETECTION_H */

Source File

Create a new source file with filepath src/slg/film/imagepipeline/plugins/overexposuredetection.cpp

/***************************************************************************
 * Copyright 1998-2017 by authors (see AUTHORS.txt)                        *
 *                                                                         *
 *   This file is part of LuxRender.                                       *
 *                                                                         *
 * Licensed under the Apache License, Version 2.0 (the "License");         *
 * you may not use this file except in compliance with the License.        *
 * You may obtain a copy of the License at                                 *
 *                                                                         *
 *     http://www.apache.org/licenses/LICENSE-2.0                          *
 *                                                                         *
 * Unless required by applicable law or agreed to in writing, software     *
 * distributed under the License is distributed on an "AS IS" BASIS,       *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.*
 * See the License for the specific language governing permissions and     *
 * limitations under the License.                                          *
 ***************************************************************************/

#include <stdexcept>
#include <boost/foreach.hpp>
#include <boost/regex.hpp>

#include "luxrays/kernels/kernels.h"
#include "slg/kernels/kernels.h"
#include "slg/film/film.h"
#include "slg/film/imagepipeline/plugins/overexposuredetection.h"

using namespace std;
using namespace luxrays;
using namespace slg;

//------------------------------------------------------------------------------
// OverExposureDetection plugin
//------------------------------------------------------------------------------

BOOST_CLASS_EXPORT_IMPLEMENT(slg::OverExposureDetection)

OverExposureDetection::OverExposureDetection(float threshold)
	: threshold(threshold)
{}

OverExposureDetection::OverExposureDetection()
	: threshold(1000.f)
{}

ImagePipelinePlugin *OverExposureDetection::Copy() const {
	return new OverExposureDetection(threshold);
}

//------------------------------------------------------------------------------
// CPU version
//------------------------------------------------------------------------------

#if _OPENMP >= 200805
	typedef unsigned int itertype;
#else
	// Visual C++ 2013 supports only OpenMP 2.5
	typedef int itertype;
#endif

void OverExposureDetection::Apply(Film &film, const u_int index) {
	Spectrum *pixels = (Spectrum *)film.channel_IMAGEPIPELINEs[index]->GetPixels();
	const u_int pixelCount = film.GetWidth() * film.GetHeight();

	#pragma omp parallel for
	for (itertype i = 0; i < pixelCount; ++i) {
		if (*(film.channel_FRAMEBUFFER_MASK->GetPixel(index))) {
			const float brightness = pixels[i].Y();
			
			if(brightness > threshold) {
				pixels[i].c[0] = 1.f;
				pixels[i].c[1] = 0.f;
				pixels[i].c[2] = 0.f;
			}
		}
	}
}

Modifying Filmparse

Open the file src/slg/film/filmparse.cpp and include your header file, right under the last include line and before the "using namespace x" lines.

#include "slg/film/imagepipeline/plugins/overexposuredetection.h"

In the same file, go to the function Film::AllocImagePipeline. You'll see a long if/else if/... section where the plugins are initialized. Add an else if clause at the end, before the final else:

    // ...
} else if (type == "OVEREXPOSURE_DETECTION") {
    const float threshold = Clamp(props.Get(Property(prefix + ".threshold")(1000.f)).Get<float>(), 0.f, INFINITY);
    imagePipeline->AddPlugin(new OverExposureDetection(threshold));
} else
    // ...

The string for the type == "PLUGINNAME" comparison can be chosen freely (it does not occur in any other files). By convention it should be all upper case, with underscores for better readability in long names.

Next, parse the value that the user set for the "threshold" parameter in the .cfg configuration file of a luxcore scene definition. Our plugin is configured like this:

# Film image pipeline plug-ins
film.imagepipeline.0.type = TONEMAP_LINEAR
film.imagepipeline.1.type = GAMMA_CORRECTION
film.imagepipeline.1.value = 2.2

# Our plugin: detect overexposure after all other imagepipeline operations are done
# Note that after a tonemap operation, all colors are in 0..1 range
film.imagepipeline.2.type = OVEREXPOSURE_DETECTION
film.imagepipeline.2.threshold = 0.99

However, the user can also omit the film.imagepipeline.2.threshold = ... line if we define a default value in the filmparse function.

It is also a good idea to sanitize the input with a Clamp function. In our case, a threshold below 0 does not make sense.

Modifying CMakeLists.txt

Open src/slg/CMakeLists.txt
Search for set(SLG_FILM_SRCS and add your source filepath among the other imagepipeline plugins:

${LuxRays_SOURCE_DIR}/src/slg/film/imagepipeline/plugins/overexposuredetection.cpp

Compile and Test

To test your plugin, compile LuxCore: Compiling_LuxCore

After the compilation succeeds, you will need a scene to test your plugin. The easiest method is to edit one of the .cfg files in the scenes/luxball directory. For example, you could add these lines to scenes/luxball/luxball-sunset.cfg:

film.imagepipeline.2.type = OVEREXPOSURE_DETECTION
film.imagepipeline.2.threshold = 0.99

Now, run luxcoreui from the root LuxCore directory with

./bin/luxcoreui ./scenes/luxball/luxball-sunset.cfg

And you should see pixels above the threshold marked in red:

Imagepipeline tutorial result.png

OpenCL Code

ImagePipeline plugins can be accelerated with OpenCL. However, I don't know how to do this yet, so I'm leaving this section blank for now.