diff --git a/Grinder/Grinder.pro b/Grinder/Grinder.pro
index 1e5fbc4fa4b4e51425ba6c614dafcc58478370a6..dc51a21b870af908566063e347ddaac31056c72d 100644
--- a/Grinder/Grinder.pro
+++ b/Grinder/Grinder.pro
@@ -486,7 +486,9 @@ SOURCES += \
     ui/image/commands/ConvertDraftItemsUndoCommand.cpp \
     ui/image/commands/ConvertPixelsUndoCommand.cpp \
     ui/image/commands/CreateLayerUndoCommand.cpp \
-    ui/image/commands/RemoveLayerUndoCommand.cpp
+    ui/image/commands/RemoveLayerUndoCommand.cpp \
+    pipeline/blocks/MaskInputBlock.cpp \
+    engine/processors/MaskInputProcessor.cpp
 
 HEADERS += \
 	ui/mainwnd/GrinderWindow.h \
@@ -1065,7 +1067,10 @@ HEADERS += \
     ui/image/commands/LayersPixelsUndoCommand.h \
     ui/image/commands/LayersPixelsUndoCommand.impl.h \
     ui/image/commands/CreateLayerUndoCommand.h \
-    ui/image/commands/RemoveLayerUndoCommand.h
+    ui/image/commands/RemoveLayerUndoCommand.h \
+    util/MathUtils.impl.h \
+    pipeline/blocks/MaskInputBlock.h \
+    engine/processors/MaskInputProcessor.h
 
 FORMS += \
 	ui/mainwnd/GrinderWindow.ui \
diff --git a/Grinder/Version.h b/Grinder/Version.h
index 6d6c9fc0b6d95de6eb25a44aada9c8f34baea67c..777de0e247f9697a94d862a665e418dabd3fbce5 100644
--- a/Grinder/Version.h
+++ b/Grinder/Version.h
@@ -10,14 +10,14 @@
 
 #define GRNDR_INFO_TITLE		"Grinder"
 #define GRNDR_INFO_COPYRIGHT	"Copyright (c) WWU Muenster"
-#define GRNDR_INFO_DATE			"13.11.2019"
+#define GRNDR_INFO_DATE			"30.12.2019"
 #define GRNDR_INFO_COMPANY		"WWU Muenster"
 #define GRNDR_INFO_WEBSITE		"http://www.uni-muenster.de"
 
 #define GRNDR_VERSION_MAJOR		0
 #define GRNDR_VERSION_MINOR		16
 #define GRNDR_VERSION_REVISION	0
-#define GRNDR_VERSION_BUILD		414
+#define GRNDR_VERSION_BUILD		419
 
 namespace grndr
 {
diff --git a/Grinder/common/properties/PropertyID.cpp b/Grinder/common/properties/PropertyID.cpp
index ff16eaae3f01780dbf4a0e9d5c9ac10c97ce5ba5..edcc88574436708153898f6756db741106e71109 100644
--- a/Grinder/common/properties/PropertyID.cpp
+++ b/Grinder/common/properties/PropertyID.cpp
@@ -73,4 +73,6 @@ const char* PropertyID::Network = "Network";
 const char* PropertyID::MaxIterations = "MaxIterations";
 const char* PropertyID::DisplayInterval = "DisplayInterval";
 const char* PropertyID::SnapshotInterval = "SnapshotInterval";
+const char* PropertyID::Renderable = "Renderable";
 const char* PropertyID::State = "State";
+const char* PropertyID::Filename = "Filename";
diff --git a/Grinder/common/properties/PropertyID.h b/Grinder/common/properties/PropertyID.h
index 879f6a396d1be0ad5c81deb4617a5812cc32c4e4..9ca6d4c9c5054808bece8c6b73b22a79aa4a71e6 100644
--- a/Grinder/common/properties/PropertyID.h
+++ b/Grinder/common/properties/PropertyID.h
@@ -80,7 +80,9 @@ namespace grndr
 		static const char* MaxIterations;
 		static const char* DisplayInterval;
 		static const char* SnapshotInterval;
+		static const char* Renderable;
 		static const char* State;
+		static const char* Filename;
 
 	public:
 		using QString::QString;
diff --git a/Grinder/controller/ImageEditorController.cpp b/Grinder/controller/ImageEditorController.cpp
index 2f70c89697fbeb7c79c2b09cb7c94b9b37786691..7a3feea81efe53aab93452e13b1518d257f9e6de 100644
--- a/Grinder/controller/ImageEditorController.cpp
+++ b/Grinder/controller/ImageEditorController.cpp
@@ -865,6 +865,14 @@ void ImageEditorController::removeSelectedNodes() const
 	}
 }
 
+void ImageEditorController::createImageTag(QString name, QColor color) const
+{
+	callControllerFunction("Creating an image tag", [this, name, color]() {
+		_activeImageBuild->generateImageTag(color, name);
+		return true;
+	});
+}
+
 void ImageEditorController::assignImageTags(ImageTagsAllotmentProperty* imageTagsAllotment, const QStringList& imageTagNames, bool assign, bool clearFirst) const
 {
 	callControllerFunction("Assigning tags", [this](ImageTagsAllotmentProperty* imageTagsAllotment, const QStringList& imageTagNames, bool assign, bool clearFirst) {
@@ -901,6 +909,35 @@ void ImageEditorController::assignImageTags(ImageTagsAllotmentProperty* imageTag
 	}
 }
 
+void ImageEditorController::autoGenerateImageTags(const Layer* layer, unsigned int* createdCount) const
+{
+	if (layer)
+	{
+		auto count = callControllerFunction("Auto-generating image tags", [this](const Layer* layer) {
+			unsigned int count = 0;
+
+			if (auto inputImageTags = _activeImageBuild->inputImageTags())
+			{
+				auto colors = layer->layerPixels().data().colors();
+
+				for (auto color : colors)
+				{
+					if (!inputImageTags->tags().selectByColor(color))
+					{
+						createImageTag(color.name(), color);
+						count += 1;
+					}
+				}
+			}
+
+			return count;
+		}, layer);
+
+		if (createdCount)
+			*createdCount = count;
+	}
+}
+
 void ImageEditorController::controllerFunctionCalled() const
 {
 	// Redraw the scene after calling a controller function
diff --git a/Grinder/controller/ImageEditorController.h b/Grinder/controller/ImageEditorController.h
index 4300d4016c0d0b4c24f31926fbca9fdf6b20f320..11ef383a1e3a457c0cd4de828508e58a9799c278 100644
--- a/Grinder/controller/ImageEditorController.h
+++ b/Grinder/controller/ImageEditorController.h
@@ -95,8 +95,10 @@ namespace grndr
 		void cutSelectedNodes() const;
 		void removeSelectedNodes() const;
 
+		void createImageTag(QString name, QColor color) const;
 		void assignImageTags(ImageTagsAllotmentProperty* imageTagsAllotment, const QStringList& imageTagNames, bool assign = true, bool clearFirst = false) const;
 		void assignImageTags(ImageTagsAllotmentProperty* imageTagsAllotment, const std::set<ImageTag*>& imageTags, bool assign = true, bool clearFirst = false) const;
+		void autoGenerateImageTags(const Layer* layer, unsigned int* createdCount = nullptr) const;
 
 	public:
 		Restrictions& restrictions() { return _restrictions; }
diff --git a/Grinder/controller/PipelineController.h b/Grinder/controller/PipelineController.h
index d1a4dab7efffea41012de8fa0d37d115bea444fa..5e10cdb2c1e5f9823fe6fb968705282902e58631 100644
--- a/Grinder/controller/PipelineController.h
+++ b/Grinder/controller/PipelineController.h
@@ -23,7 +23,6 @@ namespace grndr
 	class GraphView;
 	class GraphScene;
 	class ImageReference;
-	class BatchInputProviderBlock;
 
 	class PipelineController : public GenericController
 	{
diff --git a/Grinder/engine/processors/CanvasProcessor.cpp b/Grinder/engine/processors/CanvasProcessor.cpp
index 9422d1b053304a3ffa852fc06b13a3e68360da00..777818672d5d47d458770acab6c4d76f645deae7 100644
--- a/Grinder/engine/processors/CanvasProcessor.cpp
+++ b/Grinder/engine/processors/CanvasProcessor.cpp
@@ -42,7 +42,7 @@ void CanvasProcessor::execute(EngineExecutionContext& ctx)
 			}
 
 			if (!layersData.empty())
-				imageBuild->generateLayers(layersData, *_block->colorKey(), _block->tolerance()->getRelativeValue(), *_block->usePerceivedDistance());
+				imageBuild->generateLayers(layersData, *_block->inLayersRenderable(), *_block->colorKey(), _block->tolerance()->getRelativeValue(), *_block->usePerceivedDistance());
 
 			// Render the entire image build to provide it as this block's output
 			cv::Mat renderedImage = imageBuild->renderImageBuild();
diff --git a/Grinder/engine/processors/MaskInputProcessor.cpp b/Grinder/engine/processors/MaskInputProcessor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f9241c0f7d7e28382987baf04a638307c4309998
--- /dev/null
+++ b/Grinder/engine/processors/MaskInputProcessor.cpp
@@ -0,0 +1,49 @@
+/******************************************************************************
+ * File: MaskInputProcessor.cpp
+ * Date: 30.12.2019
+ *****************************************************************************/
+
+#include "Grinder.h"
+#include "MaskInputProcessor.h"
+#include "project/ImageReference.h"
+
+#include <opencv2/highgui.hpp>
+
+MaskInputProcessor::MaskInputProcessor(const Block* block) : Processor(block)
+{
+
+}
+
+void MaskInputProcessor::execute(EngineExecutionContext& ctx)
+{
+	Processor::execute(ctx);
+
+	if (auto imageReference = ctx.activeImageReference())
+	{
+		// Format the mask filename according to the provided pattern
+		QString filename = *_block->maskFilename();
+
+		if (filename.isEmpty())
+			throwProcessorException("No mask filename pattern provided");
+
+		QDir imageDir = QFileInfo{imageReference->getImageFilePath()}.dir();
+		QFileInfo imageFilename = imageReference->getImageFileName();
+		QString imageBasename = imageFilename.completeBaseName();
+		QString imageExt = imageFilename.suffix();
+
+		filename.replace("$filename", imageFilename.filePath(), Qt::CaseInsensitive);
+		filename.replace("$basename", imageBasename, Qt::CaseInsensitive);
+		filename.replace("$ext", imageExt, Qt::CaseInsensitive);
+
+		QFileInfo maskFile{imageDir, filename};
+
+		if (!maskFile.exists())
+			throwProcessorException(QString{"The mask file '%1' doesn't exist"}.arg(maskFile.filePath()));
+
+		// Load and forward the mask file
+		auto maskImage = cv::imread(maskFile.canonicalFilePath().toStdString(), cv::IMREAD_COLOR);
+		ctx.data().set(_block->outPort(), DataBlob{getPortDataDescriptor(_block->outPort()), std::move(maskImage)});
+	}
+	else
+		throwProcessorException("There currently is no active image");
+}
diff --git a/Grinder/engine/processors/MaskInputProcessor.h b/Grinder/engine/processors/MaskInputProcessor.h
new file mode 100644
index 0000000000000000000000000000000000000000..3dd0b36b14fa6350952e63931dfd1e373a9dc183
--- /dev/null
+++ b/Grinder/engine/processors/MaskInputProcessor.h
@@ -0,0 +1,26 @@
+/******************************************************************************
+ * File: MaskInputProcessor.h
+ * Date: 30.12.2019
+ *****************************************************************************/
+
+#ifndef MASKINPUTPROCESSOR_H
+#define MASKINPUTPROCESSOR_H
+
+#include "engine/Processor.h"
+#include "pipeline/blocks/MaskInputBlock.h"
+
+namespace grndr
+{
+	class MaskInputProcessor : public Processor<MaskInputBlock>
+	{
+		Q_OBJECT
+
+	public:
+		MaskInputProcessor(const Block* block);
+
+	public:
+		virtual void execute(EngineExecutionContext& ctx) override;
+	};
+}
+
+#endif
diff --git a/Grinder/image/ImageBuild.cpp b/Grinder/image/ImageBuild.cpp
index 1d4ede5345bda905013f657ec96be491736d95cb..96ea208c8a654f0f5652e08e7ce7be22f8d80831 100644
--- a/Grinder/image/ImageBuild.cpp
+++ b/Grinder/image/ImageBuild.cpp
@@ -130,20 +130,33 @@ void ImageBuild::moveLayer(const Layer* layer, bool up)
 		throw ImageBuildException{this, _EXCPT("Tried to move a layer not belonging to this image build")};
 }
 
-void ImageBuild::generateLayers(const LayersGenerationData& layersData, QColor colorKey, float tolerance, bool perceivedDifference)
+void ImageBuild::generateLayers(const LayersGenerationData& layersData, bool renderable, QColor colorKey, float tolerance, bool perceivedDifference)
 {
 	for (auto layerData : layersData)
 	{
 		auto layer = _layers.selectByGeneratingBlock(layerData.first);
 
 		if (!layer)
-			layer = createLayer(QString{"In: %1"}.arg(layerData.first->getName()), Layer::Type::AutoGenerated, Layer::Flag::Renderable, nullptr, layerData.first);
+		{
+			Layer::Flags layerFlags = Layer::Flag::None;
+
+			if (renderable)
+				layerFlags |= Layer::Flag::Renderable;
+
+			layer = createLayer(QString{"In: %1"}.arg(layerData.first->getName()), Layer::Type::AutoGenerated, layerFlags, nullptr, layerData.first);
+		}
 
 		if (layer && !layer->hasFlag(Layer::Flag::PreventUpdates))
 			layer->layerPixels().data().fromMatrix(layerData.second->getMatrix(), colorKey, tolerance, perceivedDifference);
 	}
 }
 
+void ImageBuild::generateImageTag(QColor color, QString name)
+{
+	if (auto imageTags = inputImageTags())
+		imageTags->autoGenerateNamedTag(color, name);
+}
+
 void ImageBuild::autoGenerateImageTag(QColor color)
 {
 	if (auto autoGenerate = _block->portProperty<BoolProperty>(PortType::ImageTagsIn, PropertyID::AutoGenerate))
diff --git a/Grinder/image/ImageBuild.h b/Grinder/image/ImageBuild.h
index 495e9906d3a851650bec7df4bb12346911f34569..b118b1d7a42fdceb90602ddad4346053d72b7811 100644
--- a/Grinder/image/ImageBuild.h
+++ b/Grinder/image/ImageBuild.h
@@ -42,9 +42,10 @@ namespace grndr
 
 		void moveLayer(const Layer* layer, bool up);
 
-		void generateLayers(const LayersGenerationData& layersData, QColor colorKey, float tolerance, bool perceivedDifference);
+		void generateLayers(const LayersGenerationData& layersData, bool renderable, QColor colorKey, float tolerance, bool perceivedDifference);
 
 	public:
+		void generateImageTag(QColor color, QString name);
 		void autoGenerateImageTag(QColor color);
 
 	public:
diff --git a/Grinder/image/ImageTags.cpp b/Grinder/image/ImageTags.cpp
index 81cf1223ae3b50cf281102c4c9649b31e19d02f3..f59ab64f485b97f309d803533ef2c185685b175c 100644
--- a/Grinder/image/ImageTags.cpp
+++ b/Grinder/image/ImageTags.cpp
@@ -65,7 +65,7 @@ void ImageTags::removeAllTags()
 		removeTag(tag.get());
 }
 
-std::shared_ptr<ImageTag> ImageTags::autoGenerateTag(QColor color, bool checkTagLimit)
+std::shared_ptr<ImageTag> ImageTags::autoGenerateNamedTag(QColor color, QString name, bool checkTagLimit)
 {
 	auto imageTag = _tags.selectByColor(color);
 
@@ -73,7 +73,7 @@ std::shared_ptr<ImageTag> ImageTags::autoGenerateTag(QColor color, bool checkTag
 	{
 		if (!checkTagLimit || _tags.size() < Maximum_Tags_Count)
 		{
-			imageTag = makeTag(color.name(), color);
+			imageTag = makeTag(name, color);
 			addTag(imageTag);
 		}
 	}
diff --git a/Grinder/image/ImageTags.h b/Grinder/image/ImageTags.h
index f1b57c2838eef81e0e8a7adb68c050943c0c0f87..1b155b41d9a1902e15896c25735bc2eb8299a16c 100644
--- a/Grinder/image/ImageTags.h
+++ b/Grinder/image/ImageTags.h
@@ -32,7 +32,8 @@ namespace grndr
 		void removeAllTags();
 
 		std::shared_ptr<ImageTag> makeTag(QString name, QColor color = QColor{}) { return std::make_shared<ImageTag>(this, name, color); }
-		std::shared_ptr<ImageTag> autoGenerateTag(QColor color, bool checkTagLimit = true);
+		std::shared_ptr<ImageTag> autoGenerateTag(QColor color, bool checkTagLimit = true) { return autoGenerateNamedTag(color, color.name(), checkTagLimit); }
+		std::shared_ptr<ImageTag> autoGenerateNamedTag(QColor color, QString name, bool checkTagLimit = true);
 
 	public:
 		const ImageTagVector& tags() const { return _tags; }
diff --git a/Grinder/pipeline/BlockCatalog.cpp b/Grinder/pipeline/BlockCatalog.cpp
index 3333da8db55a386660aa0dd1960c2befbef23f7d..4ee8c5086aab60e56538e237aaae725a04ecd0fe 100644
--- a/Grinder/pipeline/BlockCatalog.cpp
+++ b/Grinder/pipeline/BlockCatalog.cpp
@@ -8,6 +8,7 @@
 
 #include "blocks/InputBlock.h"
 #include "blocks/BatchInputBlock.h"
+#include "blocks/MaskInputBlock.h"
 #include "blocks/CanvasBlock.h"
 #include "blocks/ConvertToGrayscaleBlock.h"
 #include "blocks/ConvertToColorBlock.h"
@@ -125,6 +126,7 @@ void BlockCatalog::registerStandardBlocks()
 {
 	REGISTER_BLOCK_TYPE(InputBlock, "Provides an image of the image list.");
 	REGISTER_BLOCK_TYPE(BatchInputBlock, "Provides multiple images of the image list for batch processing.");
+	REGISTER_BLOCK_TYPE(MaskInputBlock, "Provides an additional mask for the currently active image.");
 	REGISTER_BLOCK_TYPE(CanvasBlock, "Provides the image for outputting and also allows manual editing in the image editor.");
 	REGISTER_BLOCK_TYPE(ConvertToGrayscaleBlock, "Converts its input to a grayscale image.");
 	REGISTER_BLOCK_TYPE(ConvertToColorBlock, "Converts its input to a color image of various color spaces.");
diff --git a/Grinder/pipeline/BlockType.cpp b/Grinder/pipeline/BlockType.cpp
index 687aff702929305933baac92a1d92d9883095084..99efd1a32327ab3c0157f2508f4261cd021aa572 100644
--- a/Grinder/pipeline/BlockType.cpp
+++ b/Grinder/pipeline/BlockType.cpp
@@ -10,6 +10,7 @@ const char* BlockType::Undefined = "";
 
 const char* BlockType::Input = "Input";
 const char* BlockType::BatchInput = "BatchInput";
+const char* BlockType::MaskInput = "MaskInput";
 
 const char* BlockType::_Output_ = "Output";
 const char* BlockType::Canvas = "Canvas";
diff --git a/Grinder/pipeline/BlockType.h b/Grinder/pipeline/BlockType.h
index 37de58364314c3f484b339e837045bb159705ebf..3bbc9eb6e1c5ff88765be1c808783e62a68617dd 100644
--- a/Grinder/pipeline/BlockType.h
+++ b/Grinder/pipeline/BlockType.h
@@ -17,6 +17,7 @@ namespace grndr
 
 		static const char* Input;
 		static const char* BatchInput;
+		static const char* MaskInput;
 
 		static const char* _Output_;	/* Obsolete */
 		static const char* Canvas;
diff --git a/Grinder/pipeline/blocks/CanvasBlock.cpp b/Grinder/pipeline/blocks/CanvasBlock.cpp
index e37287646b4e80309b33842b1dc32a1d1fac62df..c4c584a7f6c3911f54edba66559368d9324c0b47 100644
--- a/Grinder/pipeline/blocks/CanvasBlock.cpp
+++ b/Grinder/pipeline/blocks/CanvasBlock.cpp
@@ -26,6 +26,9 @@ void CanvasBlock::createProperties()
 
 	setPropertyGroup("Layers");
 
+	_inLayersRenderable = createProperty<BoolProperty>(PropertyID::Renderable, "Renderable", true);
+	inLayersRenderable()->setDescription("Whether layers generated from incoming images connected to the Layers port are renderable by default.");
+
 	_colorKey = createProperty<ColorProperty>(PropertyID::Color, "Background color", QColor{255, 0, 255});
 	colorKey()->setDescription("The background color to skip when generating layers from incoming images connected to the Layers port.");
 
diff --git a/Grinder/pipeline/blocks/CanvasBlock.h b/Grinder/pipeline/blocks/CanvasBlock.h
index d479c9df89662be00fba07b4f0f15dbe7b77ad85..d31378436a79bb34f5c0bce0fdbc69293b395cdb 100644
--- a/Grinder/pipeline/blocks/CanvasBlock.h
+++ b/Grinder/pipeline/blocks/CanvasBlock.h
@@ -25,6 +25,8 @@ namespace grndr
 		virtual std::unique_ptr<ProcessorBase> createProcessor() const override;
 
 	public:
+		auto inLayersRenderable() { return dynamic_cast<BoolProperty*>(_inLayersRenderable.get()); }
+		auto inLayersRenderable() const { return dynamic_cast<const BoolProperty*>(_inLayersRenderable.get()); }
 		auto colorKey() { return dynamic_cast<ColorProperty*>(_colorKey.get()); }
 		auto colorKey() const { return dynamic_cast<const ColorProperty*>(_colorKey.get()); }
 		auto tolerance() { return dynamic_cast<PercentProperty*>(_tolerance.get()); }
@@ -52,6 +54,7 @@ namespace grndr
 		virtual void createPorts() override;
 
 	private:
+		std::shared_ptr<PropertyBase> _inLayersRenderable;
 		std::shared_ptr<PropertyBase> _colorKey;
 		std::shared_ptr<PropertyBase> _tolerance;
 		std::shared_ptr<PropertyBase> _usePerceivedDistance;
diff --git a/Grinder/pipeline/blocks/MaskInputBlock.cpp b/Grinder/pipeline/blocks/MaskInputBlock.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e75d834bc65098ad23ddf23a39773f3072798a6e
--- /dev/null
+++ b/Grinder/pipeline/blocks/MaskInputBlock.cpp
@@ -0,0 +1,46 @@
+/******************************************************************************
+ * File: MaskInputBlock.cpp
+ * Date: 30.12.2019
+ *****************************************************************************/
+
+#include "Grinder.h"
+#include "MaskInputBlock.h"
+#include "engine/processors/MaskInputProcessor.h"
+
+const BlockType MaskInputBlock::type_value = BlockType::MaskInput;
+const BlockCategory MaskInputBlock::category_value = BlockCategory::Input;
+
+MaskInputBlock::MaskInputBlock(Pipeline* pipeline, QString name) : Block(pipeline, type_value, category_value, name)
+{
+	_bypassPossible = false;
+}
+
+std::unique_ptr<ProcessorBase> MaskInputBlock::createProcessor() const
+{
+	return std::make_unique<MaskInputProcessor>(this);
+}
+
+void MaskInputBlock::createProperties()
+{
+	Block::createProperties();
+
+	setPropertyGroup("Mask");
+
+	QMap<QString, QString> placeholders;
+	placeholders["basename"] = "Base filename (w/o extension)";
+	placeholders["ext"] = "Filename extension (w/o dot)";
+	placeholders["filename"] = "Full filename (w/ extension)";
+
+	QStringList placeholdersList;
+
+	for (auto placeholder : placeholders.keys())
+		placeholdersList << QString{"<em>$%1:</em> %2"}.arg(placeholder).arg(placeholders[placeholder]);
+
+	_maskFilename = createProperty<StringProperty>(PropertyID::Filename, "Filename", "");
+	maskFilename()->setDescription(QString{"The filename pattern for the mask; the following placeholders are supported:<br><br>%1"}.arg(placeholdersList.join("<br>")));
+}
+
+void MaskInputBlock::createPorts()
+{
+	_outPort = createPort(PortType::ImageOut, Port::Direction::Out, {DataDescriptor::imageDescriptor()}, "Out");
+}
diff --git a/Grinder/pipeline/blocks/MaskInputBlock.h b/Grinder/pipeline/blocks/MaskInputBlock.h
new file mode 100644
index 0000000000000000000000000000000000000000..199e3671d292c19a67444487bb90381904e2d5fa
--- /dev/null
+++ b/Grinder/pipeline/blocks/MaskInputBlock.h
@@ -0,0 +1,45 @@
+/******************************************************************************
+ * File: MaskInputBlock.h
+ * Date: 30.12.2019
+ *****************************************************************************/
+
+#ifndef MASKINPUTBLOCK_H
+#define MASKINPUTBLOCK_H
+
+#include "pipeline/Block.h"
+
+namespace grndr
+{
+	class MaskInputBlock : public Block
+	{
+		Q_OBJECT
+
+	public:
+		static const BlockType type_value;
+		static const BlockCategory category_value;
+
+	public:
+		MaskInputBlock(Pipeline* pipeline, QString name = "");
+
+	public:
+		virtual std::unique_ptr<ProcessorBase> createProcessor() const override;
+
+	public:
+		auto maskFilename() { return dynamic_cast<StringProperty*>(_maskFilename.get()); }
+		auto maskFilename() const { return dynamic_cast<const StringProperty*>(_maskFilename.get()); }
+
+		Port* outPort() { return _outPort.get(); }
+		const Port* outPort() const { return _outPort.get(); }
+
+	protected:
+		virtual void createProperties() override;
+		virtual void createPorts() override;
+
+	private:
+		std::shared_ptr<PropertyBase> _maskFilename;
+
+		std::shared_ptr<Port> _outPort;
+	};
+}
+
+#endif
diff --git a/Grinder/ui/image/ImageEditorWidget.cpp b/Grinder/ui/image/ImageEditorWidget.cpp
index 48182d806a1548db1e9b0b1c89accd2dd21b9cfd..3b6599aefed9d40e545056c9e800603e8fda298a 100644
--- a/Grinder/ui/image/ImageEditorWidget.cpp
+++ b/Grinder/ui/image/ImageEditorWidget.cpp
@@ -232,7 +232,26 @@ void ImageEditorWidget::on_splitterLayersTags_splitterMoved(int pos, int index)
 
 void ImageEditorWidget::on_primaryColorWidget_customContextMenuRequested(const QPoint& pos)
 {
-	QMenu menu;
+	QMenu menu;	
+	auto createTagAction = menu.addAction("&Create image tag", this, SLOT(createImageTagFromPrimaryColor()));
+
+	// Only enable the create tag action if there is no such image tag yet
+	bool enableAction = false;
+
+	if (auto imageBuild = _imageEditor->controller().activeImageBuild())
+	{
+		if (auto imageTags = imageBuild->inputImageTags())
+		{
+			if (!imageTags->tags().selectByColor(_imageEditor->environment().getPrimaryColor()))
+				enableAction = true;
+		}
+	}
+
+	createTagAction->setEnabled(enableAction);
+
+	menu.addSeparator();
+
+	// Create the add to presets menu
 	auto presetsMenu = menu.addMenu("&Assign to preset");
 
 	for (unsigned int i = 0; i < ui->colorPresetsWidget->getPresetsCount(); ++i)
@@ -321,6 +340,16 @@ void ImageEditorWidget::assignPrimaryColorToPreset()
 	}
 }
 
+void ImageEditorWidget::createImageTagFromPrimaryColor()
+{
+	QColor color = _imageEditor->environment().getPrimaryColor();
+	bool ok = false;
+	QString tagName = QInputDialog::getText(this, "Generate image tag", "Image tag name:", QLineEdit::EchoMode::Normal, color.name(), &ok);
+
+	if (ok && !tagName.isEmpty())
+		_imageEditor->controller().createImageTag(tagName, color);
+}
+
 void ImageEditorWidget::undoCommand() const
 {
 	_imageEditor->controller().undoStack().undo();
diff --git a/Grinder/ui/image/ImageEditorWidget.h b/Grinder/ui/image/ImageEditorWidget.h
index 8f7754255a07269f2ff2c0acfcdb6a925841a608..bbf13fc15ec299b5086d9ea2f6904c6ed4e220ce 100644
--- a/Grinder/ui/image/ImageEditorWidget.h
+++ b/Grinder/ui/image/ImageEditorWidget.h
@@ -73,6 +73,7 @@ namespace grndr
 		void primaryColorSelected(QColor color, bool byUser);
 		void colorPresetSelected(QColor color);
 		void assignPrimaryColorToPreset();
+		void createImageTagFromPrimaryColor();
 
 		void undoCommand() const;
 
diff --git a/Grinder/ui/image/LayersListWidget.cpp b/Grinder/ui/image/LayersListWidget.cpp
index 1bf3c5ca4bea6c8004b186fe2a8c6c1a78b0b20d..ac81490aa3871033474c62c970be98cdb7ee530d 100644
--- a/Grinder/ui/image/LayersListWidget.cpp
+++ b/Grinder/ui/image/LayersListWidget.cpp
@@ -9,6 +9,7 @@
 #include "ImageEditor.h"
 #include "ImageTagsListWidget.h"
 #include "image/ImageBuild.h"
+#include "image/ImageTags.h"
 #include "core/GrinderApplication.h"
 #include "ui/widgets/ControlBar.h"
 #include "ui/widgets/SliderValueWidget.h"
@@ -44,6 +45,7 @@ LayersListWidget::LayersListWidget(QWidget* parent) : MetaWidget(parent),
 	_preventUpdatesAction = UIUtils::createAction(this, "Prevent updates", FILE_ICON_PREVENTUPDATES, SLOT(setLayerPreventUpdates()), "Prevent changes to the selected layer when executing the pipeline");
 	_preventUpdatesAction->setCheckable(true);
 	_editLayerTagsAction = UIUtils::createAction(this, "Edit layer tags...", FILE_ICON_TAGS, SLOT(editLayerTags()), "Edit the tags assigned to the layer");
+	_autoGenerateImageTagsAction = UIUtils::createAction(this, "Auto-generate image tags", "", SLOT(autoGenerateImageTags()), "Automatically generate image tags for all colors in the layer");
 
 	// Create the alpha slider action
 	auto sliderAction = new QWidgetAction{this};
@@ -79,6 +81,7 @@ void LayersListWidget::assignUiComponents(ImageEditor* imageEditor)
 	_imageEditor = imageEditor;
 
 	// Listen for controller events in order to update the UI accordingly
+	connect(&imageEditor->controller(), &ImageEditorController::imageBuildSwitching, this, &LayersListWidget::imageBuildSwitching);
 	connect(&imageEditor->controller(), &ImageEditorController::imageBuildSwitched, this, &LayersListWidget::imageBuildSwitched);
 	connect(&imageEditor->controller(), &ImageEditorController::layerSwitching, this, &LayersListWidget::layerSwitching);
 	connect(&imageEditor->controller(), &ImageEditorController::layerSwitched, this, &LayersListWidget::layerSwitched);
@@ -143,6 +146,10 @@ std::vector<QAction*> LayersListWidget::getActions(MetaWidget::AddActionsMode mo
 		actions.push_back(_editLayerAction);
 
 	actions.push_back(_editLayerTagsAction);
+
+	if (mode == AddActionsMode::ContextMenu)
+		actions.push_back(_autoGenerateImageTagsAction);
+
 	actions.push_back(nullptr);
 	actions.push_back(_convertPixelsToItemsAction);
 	actions.push_back(nullptr);
@@ -362,10 +369,33 @@ void LayersListWidget::editLayerTags()
 	}
 }
 
+void LayersListWidget::autoGenerateImageTags()
+{
+	if (auto layer = currentObject())
+	{
+		unsigned int count = 0;
+		_imageEditor->controller().autoGenerateImageTags(layer, &count);
+
+		if (count > 0)
+			QMessageBox::information(this, "Image tags generation", QString{"%1 image tag(s) have been created."}.arg(count));
+		else
+			QMessageBox::information(this, "Image tags generation", "No image tags have been created.");
+	}
+}
+
+void LayersListWidget::imageBuildSwitching(ImageBuild* imageBuild)
+{
+	if (imageBuild)
+		disconnect(imageBuild, &ImageBuild::inputImageTagsChanged, this, &LayersListWidget::updateActions);
+}
+
 void LayersListWidget::imageBuildSwitched(ImageBuild* imageBuild)
 {
 	// A new image build is shown in the editor, so populate its layers
 	populateList(imageBuild->layers(), true, true);
+
+	// Whenever the input image tags have changed, update the actions
+	connect(imageBuild, &ImageBuild::inputImageTagsChanged, this, &LayersListWidget::updateActions);
 }
 
 void LayersListWidget::layerCheckChanged(QListWidgetItem* item) const
@@ -410,6 +440,7 @@ void LayersListWidget::updateActions()
 {
 	auto currentLayer = currentObject();
 	bool layerSelected = (currentLayer != nullptr);
+	bool imageTagsAvailable = (currentLayer && currentLayer->imageBuild()->inputImageTags());
 	int removableLayersCount = 0;
 
 	for (int i = 0; i < count(); ++i)
@@ -431,6 +462,7 @@ void LayersListWidget::updateActions()
 	_renderableAction->setEnabled(layerSelected);
 	_preventUpdatesAction->setEnabled(layerSelected);
 	_editLayerTagsAction->setEnabled(layerSelected);
+	_autoGenerateImageTagsAction->setEnabled(layerSelected && imageTagsAvailable);
 	_removeAllLayersAction->setEnabled(removableLayersCount > 0);
 
 	_sliderValueWidget->setValue(currentLayer ? currentLayer->getAlpha() : 0);
diff --git a/Grinder/ui/image/LayersListWidget.h b/Grinder/ui/image/LayersListWidget.h
index 4f4909b509814ff62cf58ba581a1041ff3882182..857f53dca6d6a2f3b3c2ee3be890d2e5400a03a1 100644
--- a/Grinder/ui/image/LayersListWidget.h
+++ b/Grinder/ui/image/LayersListWidget.h
@@ -72,7 +72,9 @@ namespace grndr
 		void setLayerRenderable();
 		void setLayerPreventUpdates();
 		void editLayerTags();
+		void autoGenerateImageTags();
 
+		void imageBuildSwitching(ImageBuild* imageBuild);
 		void imageBuildSwitched(ImageBuild* imageBuild);
 
 		void layerCheckChanged(QListWidgetItem* item) const;
@@ -108,6 +110,7 @@ namespace grndr
 		QAction* _renderableAction{nullptr};
 		QAction* _preventUpdatesAction{nullptr};
 		QAction* _editLayerTagsAction{nullptr};
+		QAction* _autoGenerateImageTagsAction{nullptr};
 	};
 }
 
diff --git a/Grinder/ui/mainwnd/ImageReferencesListWidget.cpp b/Grinder/ui/mainwnd/ImageReferencesListWidget.cpp
index 28be1bb953e3cd8af2401a2af20e210ccb337566..7bc28975e19a790b767dc3c9be87f38bd13ed2b4 100644
--- a/Grinder/ui/mainwnd/ImageReferencesListWidget.cpp
+++ b/Grinder/ui/mainwnd/ImageReferencesListWidget.cpp
@@ -1,207 +1,232 @@
-/******************************************************************************
- * File: ImageReferencesListWidget.cpp
- * Date: 10.2.2018
- *****************************************************************************/
-
-#include "Grinder.h"
-#include "ImageReferencesListWidget.h"
-#include "core/GrinderApplication.h"
-#include "project/Project.h"
-#include "ui/widgets/ControlBar.h"
-#include "ui/UIUtils.h"
-#include "image/ImageUtils.h"
-#include "util/FileUtils.h"
-#include "res/Resources.h"
-
-ImageReferencesListWidget::ImageReferencesListWidget(QWidget* parent) : MetaWidget(parent)
-{
-	setAcceptDrops(true);
-
-	// Create labels actions
-	_switchImageReferenceAction = UIUtils::createAction(this, "&Activate", FILE_ICON_ACTIVATE, SLOT(switchImageReference()), "Activate the selected image", "Return");
-	_nextImageReferenceAction = UIUtils::createAction(this, "Switch to &next image", "", SLOT(nextImageReference()), "Activate the next image", "Ctrl+PgDown", Qt::WindowShortcut);
-	_previousImageReferenceAction = UIUtils::createAction(this, "Switch to &previous image", "", SLOT(previousImageReference()), "Activate the previous image", "Ctrl+PgUp", Qt::WindowShortcut);
-	_viewImageReferenceAction = UIUtils::createAction(this, "&View image", FILE_ICON_VIEWIMAGE, SLOT(viewImageReference()), "View the selected image");
-	_addImageReferencesAction = UIUtils::createAction(this, "Add &images", FILE_ICON_ADD, SLOT(addImageReferences()), "Add one or more images to the project", "Ctrl+I", Qt::WindowShortcut);
-	_removeImageReferenceAction = UIUtils::createAction(this, "&Remove image", FILE_ICON_DELETE, SLOT(removeImageReference()), "Remove the selected image", "Del");
-	_removeAllImageReferencesAction = UIUtils::createAction(this, "Remove all images", "", SLOT(removeAllImageReferences()), "Remove all images");
-
-	// Listen for item selections in order to update the actions
-	connect(this, &ImageReferencesListWidget::itemSelectionChanged, this, &ImageReferencesListWidget::updateActions);
-
-	// Listen for label switching in order to update the active state flags
-	connect(&grinder()->projectController(), &ProjectController::imageReferenceSwitched, this, &ImageReferencesListWidget::imageReferenceSwitched);
-
-	updateActions();
-}
-
-std::unique_ptr<QMenu> ImageReferencesListWidget::createContextMenu() const
-{
-	auto menu = widget_type::createContextMenu();
-	menu->setDefaultAction(_switchImageReferenceAction);
-	return menu;
-}
-
-std::vector<QAction*> ImageReferencesListWidget::getActions(AddActionsMode mode) const
-{
-	std::vector<QAction*> actions;
-
-	if (mode == AddActionsMode::ContextMenu || mode == AddActionsMode::Toolbar)
-	{
-		actions.push_back(_switchImageReferenceAction);
-
-		if (mode == AddActionsMode::ContextMenu)
-			actions.push_back(_viewImageReferenceAction);
-
-		actions.push_back(nullptr);
-	}
-
-	if (mode == AddActionsMode::MainMenu || mode == AddActionsMode::ContextMenu)
-	{
-		actions.push_back(_nextImageReferenceAction);
-		actions.push_back(_previousImageReferenceAction);
-		actions.push_back(nullptr);
-	}
-
-	actions.push_back(_addImageReferencesAction);
-	actions.push_back(_removeImageReferenceAction);
-
-	if (mode == AddActionsMode::ContextMenu)
-	{
-		actions.push_back(nullptr);
-		actions.push_back(_removeAllImageReferencesAction);
-	}
-
-	return actions;
-}
-
-void ImageReferencesListWidget::switchToObjectItem(ImageReferencesListItem* item, bool selectItem)
-{
-	if (item)
-	{
-		grinder()->projectController().switchImageReference(item->object());
-
-		if (selectItem)
-			setCurrentItem(item);
-	}
-	else
-		grinder()->projectController().switchImageReference(nullptr);
-}
-
-void ImageReferencesListWidget::dragEnterEvent(QDragEnterEvent* event)
-{
-	if (event->mimeData()->hasUrls())
-	{
-		event->setDropAction(Qt::CopyAction);
-		event->accept();
-	}
-	else
-		event->ignore();
-}
-
-void ImageReferencesListWidget::dragMoveEvent(QDragMoveEvent* event)
-{
-	if (event->mimeData()->hasUrls())
-	{
-		event->setDropAction(Qt::CopyAction);
-		event->accept();
-	}
-	else
-		event->ignore();
-}
-
-void ImageReferencesListWidget::dropEvent(QDropEvent* event)
-{
-	if (event->mimeData()->hasUrls())
-	{
-		auto urls = event->mimeData()->urls();
-		QStringList files;
-
-		for (auto url : urls)
-			files << url.toLocalFile();
-
-		addImageReferences(files);
-
-		event->setDropAction(Qt::CopyAction);
-		event->accept();
-	}
-	else
-		event->ignore();
-}
-
-void ImageReferencesListWidget::nextImageReference() const
-{
-	grinder()->projectController().switchToNextImageReference();
-}
-
-void ImageReferencesListWidget::previousImageReference() const
-{
-	grinder()->projectController().switchToPreviousImageReference();
-}
-
-void ImageReferencesListWidget::viewImageReference()
-{
-	if (auto item = currentObjectItem())
-		item->viewImage();
-}
-
-void ImageReferencesListWidget::addImageReferences()
-{
-	auto files = UIUtils::askFileNames("ImageReferences", this, "Add images", QString{"Images (%1);;All files (*.*)"}.arg(ImageUtils::getSupportedFormatFilters().join(" ")));
-	addImageReferences(files);
-}
-
-void ImageReferencesListWidget::addImageReferences(QStringList files)
-{
-	// Expand the file list, recursively finding all image files, and create image references for them	
-	files = FileUtils::expandFileList(files, ImageUtils::getSupportedFormatFilters());
-	auto imageRefs = grinder()->projectController().createImageReferences(files);
-
-	// If no active image exists, switch to the last added one
-	if (!grinder()->projectController().activeImageReference())
-	{
-		if (!imageRefs.empty())
-		{
-			auto item = findObjectItem(imageRefs[imageRefs.size() - 1].get());
-
-			if (item)
-				switchToObjectItem(item);
-		}
-	}
-}
-
-void ImageReferencesListWidget::removeImageReference()
-{
-	if (auto imageRef = currentObject())
-	{
-		grinder()->projectController().removeImageReference(imageRef);
-
-		// Switch to the image reference that is currently selected if no active one exists
-		if (!grinder()->projectController().activeImageReference())
-			switchToObjectItem(currentObjectItem());
-	}
-}
-
-void ImageReferencesListWidget::removeAllImageReferences()
-{
-	grinder()->projectController().removeAllImageReferences();
-	updateActions();
-}
-
-void ImageReferencesListWidget::imageReferenceSwitched(ImageReference* imageRef)
-{
-	objectSwitched(imageRef);
-	updateActions();
-}
-
-void ImageReferencesListWidget::updateActions()
-{
-	bool imageSelected = (currentObjectItem() != nullptr);
-
-	_switchImageReferenceAction->setEnabled(imageSelected && !currentObjectItem()->isActive());
-	_nextImageReferenceAction->setEnabled(count() > 1);
-	_previousImageReferenceAction->setEnabled(count() > 1);
-	_viewImageReferenceAction->setEnabled(imageSelected);
-	_removeImageReferenceAction->setEnabled(imageSelected);
-	_removeAllImageReferencesAction->setEnabled(count() > 0);
-}
+/******************************************************************************
+ * File: ImageReferencesListWidget.cpp
+ * Date: 10.2.2018
+ *****************************************************************************/
+
+#include "Grinder.h"
+#include "ImageReferencesListWidget.h"
+#include "core/GrinderApplication.h"
+#include "project/Project.h"
+#include "ui/widgets/ControlBar.h"
+#include "ui/UIUtils.h"
+#include "image/ImageUtils.h"
+#include "util/FileUtils.h"
+#include "res/Resources.h"
+
+ImageReferencesListWidget::ImageReferencesListWidget(QWidget* parent) : MetaWidget(parent)
+{
+	setAcceptDrops(true);
+
+	// Create labels actions
+	_switchImageReferenceAction = UIUtils::createAction(this, "&Activate", FILE_ICON_ACTIVATE, SLOT(switchImageReference()), "Activate the selected image", "Return");
+	_nextImageReferenceAction = UIUtils::createAction(this, "Switch to &next image", "", SLOT(nextImageReference()), "Activate the next image", "Ctrl+PgDown", Qt::WindowShortcut);
+	_previousImageReferenceAction = UIUtils::createAction(this, "Switch to &previous image", "", SLOT(previousImageReference()), "Activate the previous image", "Ctrl+PgUp", Qt::WindowShortcut);
+	_viewImageReferenceAction = UIUtils::createAction(this, "&View image", FILE_ICON_VIEWIMAGE, SLOT(viewImageReference()), "View the selected image");
+	_addImageReferencesAction = UIUtils::createAction(this, "Add &images", FILE_ICON_ADD, SLOT(addImageReferences()), "Add one or more images to the project", "Ctrl+I", Qt::WindowShortcut);
+	_removeImageReferenceAction = UIUtils::createAction(this, "&Remove image", FILE_ICON_DELETE, SLOT(removeImageReference()), "Remove the selected image", "Del");
+	_removeAllImageReferencesAction = UIUtils::createAction(this, "Remove all images", "", SLOT(removeAllImageReferences()), "Remove all images");
+
+#if defined(Q_OS_WIN)
+	_showInFileBrowserAction = UIUtils::createAction(this, "&Show in explorer", FILE_ICON_OPEN, SLOT(showInFileBrowser()), "Show the image file in Windows Explorer");
+#else
+	_showInFileBrowserAction = UIUtils::createAction(this, "&Show in finder", FILE_ICON_OPEN, SLOT(showInFileBrowser()), "Show the image file in Finder");
+#endif
+
+	// Listen for item selections in order to update the actions
+	connect(this, &ImageReferencesListWidget::itemSelectionChanged, this, &ImageReferencesListWidget::updateActions);
+
+	// Listen for label switching in order to update the active state flags
+	connect(&grinder()->projectController(), &ProjectController::imageReferenceSwitched, this, &ImageReferencesListWidget::imageReferenceSwitched);
+
+	updateActions();
+}
+
+std::unique_ptr<QMenu> ImageReferencesListWidget::createContextMenu() const
+{
+	auto menu = widget_type::createContextMenu();
+	menu->setDefaultAction(_switchImageReferenceAction);
+	return menu;
+}
+
+std::vector<QAction*> ImageReferencesListWidget::getActions(AddActionsMode mode) const
+{
+	std::vector<QAction*> actions;
+
+	if (mode == AddActionsMode::ContextMenu || mode == AddActionsMode::Toolbar)
+	{
+		actions.push_back(_switchImageReferenceAction);
+
+		if (mode == AddActionsMode::ContextMenu)
+		{
+			actions.push_back(_viewImageReferenceAction);
+
+#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
+			actions.push_back(nullptr);
+			actions.push_back(_showInFileBrowserAction);
+#endif
+		}
+
+		actions.push_back(nullptr);
+	}
+
+	if (mode == AddActionsMode::MainMenu || mode == AddActionsMode::ContextMenu)
+	{
+		actions.push_back(_nextImageReferenceAction);
+		actions.push_back(_previousImageReferenceAction);
+		actions.push_back(nullptr);
+	}
+
+	actions.push_back(_addImageReferencesAction);
+	actions.push_back(_removeImageReferenceAction);
+
+	if (mode == AddActionsMode::ContextMenu)
+	{
+		actions.push_back(nullptr);
+		actions.push_back(_removeAllImageReferencesAction);
+	}
+
+	return actions;
+}
+
+void ImageReferencesListWidget::switchToObjectItem(ImageReferencesListItem* item, bool selectItem)
+{
+	if (item)
+	{
+		grinder()->projectController().switchImageReference(item->object());
+
+		if (selectItem)
+			setCurrentItem(item);
+	}
+	else
+		grinder()->projectController().switchImageReference(nullptr);
+}
+
+void ImageReferencesListWidget::dragEnterEvent(QDragEnterEvent* event)
+{
+	if (event->mimeData()->hasUrls())
+	{
+		event->setDropAction(Qt::CopyAction);
+		event->accept();
+	}
+	else
+		event->ignore();
+}
+
+void ImageReferencesListWidget::dragMoveEvent(QDragMoveEvent* event)
+{
+	if (event->mimeData()->hasUrls())
+	{
+		event->setDropAction(Qt::CopyAction);
+		event->accept();
+	}
+	else
+		event->ignore();
+}
+
+void ImageReferencesListWidget::dropEvent(QDropEvent* event)
+{
+	if (event->mimeData()->hasUrls())
+	{
+		auto urls = event->mimeData()->urls();
+		QStringList files;
+
+		for (auto url : urls)
+			files << url.toLocalFile();
+
+		addImageReferences(files);
+
+		event->setDropAction(Qt::CopyAction);
+		event->accept();
+	}
+	else
+		event->ignore();
+}
+
+void ImageReferencesListWidget::nextImageReference() const
+{
+	grinder()->projectController().switchToNextImageReference();
+}
+
+void ImageReferencesListWidget::previousImageReference() const
+{
+	grinder()->projectController().switchToPreviousImageReference();
+}
+
+void ImageReferencesListWidget::viewImageReference()
+{
+	if (auto item = currentObjectItem())
+		item->viewImage();
+}
+
+void ImageReferencesListWidget::addImageReferences()
+{
+	auto files = UIUtils::askFileNames("ImageReferences", this, "Add images", QString{"Images (%1);;All files (*.*)"}.arg(ImageUtils::getSupportedFormatFilters().join(" ")));
+	addImageReferences(files);
+}
+
+void ImageReferencesListWidget::addImageReferences(QStringList files)
+{
+	// Expand the file list, recursively finding all image files, and create image references for them	
+	files = FileUtils::expandFileList(files, ImageUtils::getSupportedFormatFilters());
+	auto imageRefs = grinder()->projectController().createImageReferences(files);
+
+	// If no active image exists, switch to the last added one
+	if (!grinder()->projectController().activeImageReference())
+	{
+		if (!imageRefs.empty())
+		{
+			auto item = findObjectItem(imageRefs[imageRefs.size() - 1].get());
+
+			if (item)
+				switchToObjectItem(item);
+		}
+	}
+}
+
+void ImageReferencesListWidget::removeImageReference()
+{
+	if (auto imageRef = currentObject())
+	{
+		grinder()->projectController().removeImageReference(imageRef);
+
+		// Switch to the image reference that is currently selected if no active one exists
+		if (!grinder()->projectController().activeImageReference())
+			switchToObjectItem(currentObjectItem());
+	}
+}
+
+void ImageReferencesListWidget::removeAllImageReferences()
+{
+	grinder()->projectController().removeAllImageReferences();
+	updateActions();
+}
+
+void ImageReferencesListWidget::showInFileBrowser()
+{
+	if (auto imageRef = currentObject())
+		FileUtils::openInFileBrowser(imageRef->getImageFilePath());
+}
+
+void ImageReferencesListWidget::imageReferenceSwitched(ImageReference* imageRef)
+{
+	objectSwitched(imageRef);
+	updateActions();
+}
+
+void ImageReferencesListWidget::updateActions()
+{
+	bool imageSelected = (currentObjectItem() != nullptr);
+
+	_switchImageReferenceAction->setEnabled(imageSelected && !currentObjectItem()->isActive());
+	_nextImageReferenceAction->setEnabled(count() > 1);
+	_previousImageReferenceAction->setEnabled(count() > 1);
+	_viewImageReferenceAction->setEnabled(imageSelected);
+	_removeImageReferenceAction->setEnabled(imageSelected);
+	_removeAllImageReferencesAction->setEnabled(count() > 0);
+
+#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
+	_showInFileBrowserAction->setEnabled(imageSelected);
+#else
+	_showInFileBrowserAction->setEnabled(false);
+#endif
+}
diff --git a/Grinder/ui/mainwnd/ImageReferencesListWidget.h b/Grinder/ui/mainwnd/ImageReferencesListWidget.h
index 3c31592925c937ec00cfd8e73276e6e34d962c3a..fc433da534ac4502bfd1dfd015207897bfe3fd0b 100644
--- a/Grinder/ui/mainwnd/ImageReferencesListWidget.h
+++ b/Grinder/ui/mainwnd/ImageReferencesListWidget.h
@@ -1,65 +1,67 @@
-/******************************************************************************
- * File: ImageReferencesListWidget.h
- * Date: 10.2.2018
- *****************************************************************************/
-
-#ifndef IMAGEREFERENCESLISTWIDGET_H
-#define IMAGEREFERENCESLISTWIDGET_H
-
-#include "ui/widgets/MetaWidget.h"
-#include "ui/widgets/ActiveObjectListWidget.h"
-#include "ImageReferencesListItem.h"
-
-namespace grndr
-{
-	class ControlBar;
-	class ImageReference;
-
-	using ImageReferenceObjectListWidget = ActiveObjectListWidget<ImageReference, ImageReferencesListItem>;
-
-	class ImageReferencesListWidget : public MetaWidget<ImageReferenceObjectListWidget>
-	{
-		Q_OBJECT
-
-	public:
-		ImageReferencesListWidget(QWidget* parent = nullptr);
-
-	protected:
-		virtual std::unique_ptr<QMenu> createContextMenu() const override;
-
-		virtual std::vector<QAction*> getActions(AddActionsMode mode) const override;
-
-	protected:
-		virtual void switchToObjectItem(ImageReferencesListItem* item, bool selectItem = true) override;
-
-	protected:
-		virtual void dragEnterEvent(QDragEnterEvent* event) override;
-		virtual void dragMoveEvent(QDragMoveEvent* event) override;
-		virtual void dropEvent(QDropEvent* event) override;
-
-	private slots:
-		void switchImageReference() { switchToObjectItem(currentObjectItem()); }
-		void nextImageReference() const;
-		void previousImageReference() const;
-		void viewImageReference();
-		void addImageReferences();
-		void addImageReferences(QStringList files);
-		void removeImageReference();
-		void removeAllImageReferences();
-
-		void imageReferenceSwitched(ImageReference* imageRef);
-
-		virtual void updateActions() override;
-
-	private:
-		QAction* _switchImageReferenceAction{nullptr};
-		QAction* _nextImageReferenceAction{nullptr};
-		QAction* _previousImageReferenceAction{nullptr};
-		QAction* _viewImageReferenceAction{nullptr};
-		QAction* _addImageReferencesAction{nullptr};
-		QAction* _removeImageReferenceAction{nullptr};
-		QAction* _removeAllImageReferencesAction{nullptr};
-	};
-}
-
-#endif
+/******************************************************************************
+ * File: ImageReferencesListWidget.h
+ * Date: 10.2.2018
+ *****************************************************************************/
+
+#ifndef IMAGEREFERENCESLISTWIDGET_H
+#define IMAGEREFERENCESLISTWIDGET_H
+
+#include "ui/widgets/MetaWidget.h"
+#include "ui/widgets/ActiveObjectListWidget.h"
+#include "ImageReferencesListItem.h"
+
+namespace grndr
+{
+	class ControlBar;
+	class ImageReference;
+
+	using ImageReferenceObjectListWidget = ActiveObjectListWidget<ImageReference, ImageReferencesListItem>;
+
+	class ImageReferencesListWidget : public MetaWidget<ImageReferenceObjectListWidget>
+	{
+		Q_OBJECT
+
+	public:
+		ImageReferencesListWidget(QWidget* parent = nullptr);
+
+	protected:
+		virtual std::unique_ptr<QMenu> createContextMenu() const override;
+
+		virtual std::vector<QAction*> getActions(AddActionsMode mode) const override;
+
+	protected:
+		virtual void switchToObjectItem(ImageReferencesListItem* item, bool selectItem = true) override;
+
+	protected:
+		virtual void dragEnterEvent(QDragEnterEvent* event) override;
+		virtual void dragMoveEvent(QDragMoveEvent* event) override;
+		virtual void dropEvent(QDropEvent* event) override;
+
+	private slots:
+		void switchImageReference() { switchToObjectItem(currentObjectItem()); }
+		void nextImageReference() const;
+		void previousImageReference() const;
+		void viewImageReference();
+		void addImageReferences();
+		void addImageReferences(QStringList files);
+		void removeImageReference();
+		void removeAllImageReferences();
+		void showInFileBrowser();
+
+		void imageReferenceSwitched(ImageReference* imageRef);
+
+		virtual void updateActions() override;
+
+	private:
+		QAction* _switchImageReferenceAction{nullptr};
+		QAction* _nextImageReferenceAction{nullptr};
+		QAction* _previousImageReferenceAction{nullptr};
+		QAction* _viewImageReferenceAction{nullptr};
+		QAction* _showInFileBrowserAction{nullptr};
+		QAction* _addImageReferencesAction{nullptr};
+		QAction* _removeImageReferenceAction{nullptr};
+		QAction* _removeAllImageReferencesAction{nullptr};
+	};
+}
+
+#endif
diff --git a/Grinder/ui/widgets/BaseListWidget.cpp b/Grinder/ui/widgets/BaseListWidget.cpp
index 8c762b5ebe79e82c831b8764fe9215a7bfba1f48..54a719c9beddd2a8d78b39b156391c7b3b0c11ce 100644
--- a/Grinder/ui/widgets/BaseListWidget.cpp
+++ b/Grinder/ui/widgets/BaseListWidget.cpp
@@ -1,66 +1,86 @@
-/******************************************************************************
- * File: BaseListWidget.cpp
- * Date: 26.4.2018
- *****************************************************************************/
-
-#include "Grinder.h"
-#include "BaseListWidget.h"
-#include "BaseListItem.h"
-
-void BaseListWidget::invertSelection()
-{
-	for (int i = 0; i < count(); ++i)
-		item(i)->setSelected(!item(i)->isSelected());
-}
-
-void BaseListWidget::toggleCheckSelectedItems()
-{
-	auto selItems = selectedItems();
-
-	if (!selItems.isEmpty())
-	{
-		bool checkItems = (selItems[0]->checkState() != Qt::Checked);
-
-		for (auto item : selItems)
-			item->setCheckState(checkItems ? Qt::Checked : Qt::Unchecked);
-	}
-}
-
-void BaseListWidget::checkAllItems()
-{
-	for (int i = 0; i < count(); ++i)
-		item(i)->setCheckState(Qt::Checked);
-}
-
-void BaseListWidget::uncheckAllItems()
-{
-	for (int i = 0; i < count(); ++i)
-		item(i)->setCheckState(Qt::Unchecked);
-}
-
-void BaseListWidget::mousePressEvent(QMouseEvent* event)
-{
-	QListWidget::mousePressEvent(event);
-
-	// Check if any knob was clicked
-	if (event->button() == Qt::LeftButton && event->modifiers() == 0)
-	{
-		if (auto itemSelected = dynamic_cast<BaseListItem*>(item(currentRow())))
-		{
-			auto itemRect = visualItemRect(itemSelected);
-
-			if (itemRect.contains(event->pos()))
-			{
-				int knobIndex = itemSelected->hitTestKnob(event->pos() - itemRect.topLeft());
-
-				if (knobIndex != -1)
-					knobPressed(static_cast<unsigned int>(knobIndex));
-			}
-		}
-	}
-}
-
-BaseListItem* BaseListWidget::listItem(const QModelIndex& index) const
-{
-	return dynamic_cast<BaseListItem*>(itemFromIndex(index));
-}
+/******************************************************************************
+ * File: BaseListWidget.cpp
+ * Date: 26.4.2018
+ *****************************************************************************/
+
+#include "Grinder.h"
+#include "BaseListWidget.h"
+#include "BaseListItem.h"
+#include "util/MathUtils.h"
+
+BaseListItem* BaseListWidget::listItem(const QModelIndex& index) const
+{
+	return dynamic_cast<BaseListItem*>(itemFromIndex(index));
+}
+
+void BaseListWidget::invertSelection()
+{
+	for (int i = 0; i < count(); ++i)
+		item(i)->setSelected(!item(i)->isSelected());
+}
+
+void BaseListWidget::toggleCheckSelectedItems()
+{
+	auto selItems = selectedItems();
+
+	if (!selItems.isEmpty())
+	{
+		bool checkItems = (selItems[0]->checkState() != Qt::Checked);
+
+		for (auto item : selItems)
+			item->setCheckState(checkItems ? Qt::Checked : Qt::Unchecked);
+	}
+}
+
+void BaseListWidget::checkAllItems()
+{
+	for (int i = 0; i < count(); ++i)
+		item(i)->setCheckState(Qt::Checked);
+}
+
+void BaseListWidget::uncheckAllItems()
+{
+	for (int i = 0; i < count(); ++i)
+		item(i)->setCheckState(Qt::Unchecked);
+}
+
+void BaseListWidget::checkRandomizedItems()
+{
+	bool ok = false;
+	int itemsToCheck = QInputDialog::getInt(this, "Item count", "Number of items to check:", 1, 1, count(), 1, &ok);
+
+	if (ok && itemsToCheck > 0)
+	{
+		auto numbersToCheck = MathUtils::uniqueRandomNumbers(itemsToCheck, 0, count() - 1);
+
+		for (int i = 0; i < count(); ++i)
+		{
+			if (std::find(numbersToCheck.cbegin(), numbersToCheck.cend(), i) != numbersToCheck.cend())
+				item(i)->setCheckState(Qt::Checked);
+			else
+				item(i)->setCheckState(Qt::Unchecked);
+		}
+	}
+}
+
+void BaseListWidget::mousePressEvent(QMouseEvent* event)
+{
+	QListWidget::mousePressEvent(event);
+
+	// Check if any knob was clicked
+	if (event->button() == Qt::LeftButton && event->modifiers() == 0)
+	{
+		if (auto itemSelected = dynamic_cast<BaseListItem*>(item(currentRow())))
+		{
+			auto itemRect = visualItemRect(itemSelected);
+
+			if (itemRect.contains(event->pos()))
+			{
+				int knobIndex = itemSelected->hitTestKnob(event->pos() - itemRect.topLeft());
+
+				if (knobIndex != -1)
+					knobPressed(static_cast<unsigned int>(knobIndex));
+			}
+		}
+	}
+}
diff --git a/Grinder/ui/widgets/BaseListWidget.h b/Grinder/ui/widgets/BaseListWidget.h
index 1c765c7cc54f3783058409b52cd3d8635e9d4090..10d6e3fb94088430ed3d51c23337c9058bb8cdbf 100644
--- a/Grinder/ui/widgets/BaseListWidget.h
+++ b/Grinder/ui/widgets/BaseListWidget.h
@@ -1,40 +1,41 @@
-/******************************************************************************
- * File: BaseListWidget.h
- * Date: 26.4.2018
- *****************************************************************************/
-
-#ifndef BASELISTWIDGET_H
-#define BASELISTWIDGET_H
-
-#include <QListWidget>
-
-namespace grndr
-{
-	class BaseListItem;
-
-	class BaseListWidget : public QListWidget
-	{
-		Q_OBJECT
-
-	public:
-		using QListWidget::QListWidget;
-
-	public:
-		BaseListItem* listItem(const QModelIndex& index) const;
-
-	public slots:
-		void invertSelection();
-
-		void toggleCheckSelectedItems();
-		void checkAllItems();
-		void uncheckAllItems();
-
-	protected:
-		virtual void mousePressEvent(QMouseEvent* event) override;
-
-	protected:
-		virtual void knobPressed(unsigned int knobIndex) { Q_UNUSED(knobIndex); }
-	};
-}
-
-#endif
+/******************************************************************************
+ * File: BaseListWidget.h
+ * Date: 26.4.2018
+ *****************************************************************************/
+
+#ifndef BASELISTWIDGET_H
+#define BASELISTWIDGET_H
+
+#include <QListWidget>
+
+namespace grndr
+{
+	class BaseListItem;
+
+	class BaseListWidget : public QListWidget
+	{
+		Q_OBJECT
+
+	public:
+		using QListWidget::QListWidget;
+
+	public:
+		BaseListItem* listItem(const QModelIndex& index) const;
+
+	public slots:
+		void invertSelection();
+
+		void toggleCheckSelectedItems();
+		void checkAllItems();
+		void uncheckAllItems();
+		void checkRandomizedItems();
+
+	protected:
+		virtual void mousePressEvent(QMouseEvent* event) override;
+
+	protected:
+		virtual void knobPressed(unsigned int knobIndex) { Q_UNUSED(knobIndex); }
+	};
+}
+
+#endif
diff --git a/Grinder/ui/widgets/CheckListWidget.h b/Grinder/ui/widgets/CheckListWidget.h
index 08f55268b96681cc8076d481983e51a898ce8a22..658d8aa7f141f08885ee7cb12a864a021880041f 100644
--- a/Grinder/ui/widgets/CheckListWidget.h
+++ b/Grinder/ui/widgets/CheckListWidget.h
@@ -1,47 +1,48 @@
-/******************************************************************************
- * File: CheckListWidget.h
- * Date: 25.4.2018
- *****************************************************************************/
-
-#ifndef CHECKLISTWIDGET_H
-#define CHECKLISTWIDGET_H
-
-#include "ObjectListWidget.h"
-
-namespace grndr
-{
-	template<typename ObjectType, typename ItemType>
-	class CheckListWidget : public ObjectListWidget<ObjectType, ItemType>
-	{
-	public:
-		using object_type = ObjectType;
-		using item_type = ItemType;
-
-	public:
-		CheckListWidget(QWidget* parent = nullptr);
-
-		template<typename ContainerType>
-		void populateList(const ContainerType& container);
-
-		template<typename ObjectReturnType = ObjectType>
-		std::vector<ObjectReturnType*> getCheckedObjects() const;
-		template<typename ObjectReturnType = ObjectType>
-		void setCheckedObjects(const std::vector<ObjectReturnType*>& checkedObjects);
-
-	protected:
-		virtual void contextMenuEvent(QContextMenuEvent* event) override;
-
-	private:
-		QAction* _unCheckSelectedItems{nullptr};
-		QAction* _checkAllItems{nullptr};
-		QAction* _uncheckAllItems{nullptr};
-
-		QAction* _selectAllAction{nullptr};
-		QAction* _deselectAllAction{nullptr};
-		QAction* _invertSelectionAction{nullptr};
-	};
-}
-
-#include "CheckListWidget.impl.h"
-
-#endif
+/******************************************************************************
+ * File: CheckListWidget.h
+ * Date: 25.4.2018
+ *****************************************************************************/
+
+#ifndef CHECKLISTWIDGET_H
+#define CHECKLISTWIDGET_H
+
+#include "ObjectListWidget.h"
+
+namespace grndr
+{
+	template<typename ObjectType, typename ItemType>
+	class CheckListWidget : public ObjectListWidget<ObjectType, ItemType>
+	{
+	public:
+		using object_type = ObjectType;
+		using item_type = ItemType;
+
+	public:
+		CheckListWidget(QWidget* parent = nullptr);
+
+		template<typename ContainerType>
+		void populateList(const ContainerType& container);
+
+		template<typename ObjectReturnType = ObjectType>
+		std::vector<ObjectReturnType*> getCheckedObjects() const;
+		template<typename ObjectReturnType = ObjectType>
+		void setCheckedObjects(const std::vector<ObjectReturnType*>& checkedObjects);
+
+	protected:
+		virtual void contextMenuEvent(QContextMenuEvent* event) override;
+
+	private:
+		QAction* _unCheckSelectedItems{nullptr};
+		QAction* _checkAllItems{nullptr};
+		QAction* _uncheckAllItems{nullptr};
+		QAction* _checkRandomizedItems{nullptr};
+
+		QAction* _selectAllAction{nullptr};
+		QAction* _deselectAllAction{nullptr};
+		QAction* _invertSelectionAction{nullptr};
+	};
+}
+
+#include "CheckListWidget.impl.h"
+
+#endif
diff --git a/Grinder/ui/widgets/CheckListWidget.impl.h b/Grinder/ui/widgets/CheckListWidget.impl.h
index ab4b3632d4865925d4bee4b987d09dd476124bd1..ff78e7e92b7727b44fbb1130d7f269d82e415e38 100644
--- a/Grinder/ui/widgets/CheckListWidget.impl.h
+++ b/Grinder/ui/widgets/CheckListWidget.impl.h
@@ -1,107 +1,110 @@
-/******************************************************************************
- * File: CheckListWidget.impl.h
- * Date: 25.4.2018
- *****************************************************************************/
-
-#include "Grinder.h"
-#include "CheckListWidget.h"
-#include "ui/UIUtils.h"
-#include "res/Resources.h"
-
-template<typename ObjectType, typename ItemType>
-CheckListWidget<ObjectType, ItemType>::CheckListWidget(QWidget* parent) : ObjectListWidget<ObjectType, ItemType>(parent)
-{
-	_unCheckSelectedItems = UIUtils::createAction(this, "&Check selected", FILE_ICON_CHECK, SLOT(toggleCheckSelectedItems()), "Check the selected items", "Space");
-	_checkAllItems = UIUtils::createAction(this, "Check &all", FILE_ICON_CHECKALL, SLOT(checkAllItems()), "Check all items", "Ctrl+Shift+A");
-	_uncheckAllItems = UIUtils::createAction(this, "Unchec&k all", "", SLOT(uncheckAllItems()), "Uncheck all items");
-
-	_selectAllAction = UIUtils::createAction(this, "&Select all", FILE_ICON_SELECTALL, SLOT(selectAll()), "Select all items", "Ctrl+A");
-	_deselectAllAction = UIUtils::createAction(this, "&Deselect all", "", SLOT(clearSelection()), "Deselect all items");
-	_invertSelectionAction = UIUtils::createAction(this, "&Invert selection", "", SLOT(invertSelection()), "Invert the current selection", "Ctrl+I");
-}
-
-template<typename ObjectType, typename ItemType>
-template<typename ContainerType>
-void CheckListWidget<ObjectType, ItemType>::populateList(const ContainerType& container)
-{
-	ObjectListWidget<ObjectType, ItemType>::populateList(container);
-
-	for (int i = 0; i < this->count(); ++i)
-	{
-		if (auto item = this->item(i))
-		{
-			item->setFlags(item->flags()|Qt::ItemIsUserCheckable);
-			item->setCheckState(Qt::Unchecked);
-		}
-	}
-}
-
-template<typename ObjectType, typename ItemType>
-template<typename ObjectReturnType>
-std::vector<ObjectReturnType*> CheckListWidget<ObjectType, ItemType>::getCheckedObjects() const
-{
-	std::vector<ObjectReturnType*> checkedItems;
-
-	for (int i = 0; i < this->count(); ++i)
-	{
-		if (auto item = dynamic_cast<ItemType*>(this->item(i)))
-		{
-			if (item->checkState() == Qt::Checked)
-				checkedItems.push_back(static_cast<ObjectReturnType*>(item->object()));
-		}
-	}
-
-	return checkedItems;
-}
-
-template<typename ObjectType, typename ItemType>
-template<typename ObjectReturnType>
-void CheckListWidget<ObjectType, ItemType>::setCheckedObjects(const std::vector<ObjectReturnType*>& checkedObjects)
-{
-	for (int i = 0; i < this->count(); ++i)
-	{
-		if (auto item = dynamic_cast<ItemType*>(this->item(i)))
-		{
-			if (std::find(checkedObjects.cbegin(), checkedObjects.cend(), static_cast<ObjectReturnType*>(item->object())) != checkedObjects.cend())
-				item->setCheckState(Qt::Checked);
-			else
-				item->setCheckState(Qt::Unchecked);
-		}
-	}
-}
-
-template<typename ObjectType, typename ItemType>
-void CheckListWidget<ObjectType, ItemType>::contextMenuEvent(QContextMenuEvent* event)
-{
-	// Update the text of the check/uncheck selected action
-	auto selItems = this->selectedItems();
-
-	if (!selItems.isEmpty())
-	{
-		bool checkItems = (selItems[0]->checkState() != Qt::Checked);
-
-		if (checkItems)
-		{
-			_unCheckSelectedItems->setText("&Check selected");
-			_unCheckSelectedItems->setStatusTip("Check the selected items");
-		}
-		else
-		{
-			_unCheckSelectedItems->setText("&Uncheck selected");
-			_unCheckSelectedItems->setStatusTip("Uncheck the selected items");
-		}
-	}
-
-	QMenu menu;
-
-	menu.addAction(_unCheckSelectedItems);
-	menu.addSeparator();
-	menu.addAction(_checkAllItems);
-	menu.addAction(_uncheckAllItems);
-	menu.addSeparator();
-	menu.addAction(_selectAllAction);
-	menu.addAction(_deselectAllAction);
-	menu.addAction(_invertSelectionAction);
-
-	menu.exec(event->globalPos());
-}
+/******************************************************************************
+ * File: CheckListWidget.impl.h
+ * Date: 25.4.2018
+ *****************************************************************************/
+
+#include "Grinder.h"
+#include "CheckListWidget.h"
+#include "ui/UIUtils.h"
+#include "res/Resources.h"
+
+template<typename ObjectType, typename ItemType>
+CheckListWidget<ObjectType, ItemType>::CheckListWidget(QWidget* parent) : ObjectListWidget<ObjectType, ItemType>(parent)
+{
+	_unCheckSelectedItems = UIUtils::createAction(this, "&Check selected", FILE_ICON_CHECK, SLOT(toggleCheckSelectedItems()), "Check the selected items", "Space");
+	_checkAllItems = UIUtils::createAction(this, "Check &all", FILE_ICON_CHECKALL, SLOT(checkAllItems()), "Check all items", "Ctrl+Shift+A");
+	_uncheckAllItems = UIUtils::createAction(this, "Unchec&k all", "", SLOT(uncheckAllItems()), "Uncheck all items");
+	_checkRandomizedItems = UIUtils::createAction(this, "Check &randomized...", "", SLOT(checkRandomizedItems()), "Check a randomized selection of items");
+
+	_selectAllAction = UIUtils::createAction(this, "&Select all", FILE_ICON_SELECTALL, SLOT(selectAll()), "Select all items", "Ctrl+A");
+	_deselectAllAction = UIUtils::createAction(this, "&Deselect all", "", SLOT(clearSelection()), "Deselect all items");
+	_invertSelectionAction = UIUtils::createAction(this, "&Invert selection", "", SLOT(invertSelection()), "Invert the current selection", "Ctrl+I");
+}
+
+template<typename ObjectType, typename ItemType>
+template<typename ContainerType>
+void CheckListWidget<ObjectType, ItemType>::populateList(const ContainerType& container)
+{
+	ObjectListWidget<ObjectType, ItemType>::populateList(container);
+
+	for (int i = 0; i < this->count(); ++i)
+	{
+		if (auto item = this->item(i))
+		{
+			item->setFlags(item->flags()|Qt::ItemIsUserCheckable);
+			item->setCheckState(Qt::Unchecked);
+		}
+	}
+}
+
+template<typename ObjectType, typename ItemType>
+template<typename ObjectReturnType>
+std::vector<ObjectReturnType*> CheckListWidget<ObjectType, ItemType>::getCheckedObjects() const
+{
+	std::vector<ObjectReturnType*> checkedItems;
+
+	for (int i = 0; i < this->count(); ++i)
+	{
+		if (auto item = dynamic_cast<ItemType*>(this->item(i)))
+		{
+			if (item->checkState() == Qt::Checked)
+				checkedItems.push_back(static_cast<ObjectReturnType*>(item->object()));
+		}
+	}
+
+	return checkedItems;
+}
+
+template<typename ObjectType, typename ItemType>
+template<typename ObjectReturnType>
+void CheckListWidget<ObjectType, ItemType>::setCheckedObjects(const std::vector<ObjectReturnType*>& checkedObjects)
+{
+	for (int i = 0; i < this->count(); ++i)
+	{
+		if (auto item = dynamic_cast<ItemType*>(this->item(i)))
+		{
+			if (std::find(checkedObjects.cbegin(), checkedObjects.cend(), static_cast<ObjectReturnType*>(item->object())) != checkedObjects.cend())
+				item->setCheckState(Qt::Checked);
+			else
+				item->setCheckState(Qt::Unchecked);
+		}
+	}
+}
+
+template<typename ObjectType, typename ItemType>
+void CheckListWidget<ObjectType, ItemType>::contextMenuEvent(QContextMenuEvent* event)
+{
+	// Update the text of the check/uncheck selected action
+	auto selItems = this->selectedItems();
+
+	if (!selItems.isEmpty())
+	{
+		bool checkItems = (selItems[0]->checkState() != Qt::Checked);
+
+		if (checkItems)
+		{
+			_unCheckSelectedItems->setText("&Check selected");
+			_unCheckSelectedItems->setStatusTip("Check the selected items");
+		}
+		else
+		{
+			_unCheckSelectedItems->setText("&Uncheck selected");
+			_unCheckSelectedItems->setStatusTip("Uncheck the selected items");
+		}
+	}
+
+	QMenu menu;
+
+	menu.addAction(_unCheckSelectedItems);
+	menu.addSeparator();
+	menu.addAction(_checkAllItems);
+	menu.addAction(_uncheckAllItems);
+	menu.addSeparator();
+	menu.addAction(_checkRandomizedItems);
+	menu.addSeparator();
+	menu.addAction(_selectAllAction);
+	menu.addAction(_deselectAllAction);
+	menu.addAction(_invertSelectionAction);
+
+	menu.exec(event->globalPos());
+}
diff --git a/Grinder/util/FileUtils.cpp b/Grinder/util/FileUtils.cpp
index 4a3e3702d2290bc8d48ccfa7a62f086d2e3a5e39..410c54a19631da43af3c004d1cd6c9ede98a5d2a 100644
--- a/Grinder/util/FileUtils.cpp
+++ b/Grinder/util/FileUtils.cpp
@@ -71,3 +71,28 @@ QString FileUtils::getTemporaryFileName(QString filename)
 	dir.mkpath(dir.path());
 	return dir.filePath(filename);
 }
+
+void FileUtils::openInFileBrowser(QString filename)
+{
+	QStringList args;
+
+	if (QFile::exists(filename))
+	{
+#if defined(Q_OS_WIN)
+		args << "/select," << QDir::toNativeSeparators(filename);
+
+		QProcess::startDetached("explorer", args);
+#elif defined(Q_OS_MAC)
+		args << "-e";
+		args << "tell application \"Finder\"";
+		args << "-e";
+		args << "activate";
+		args << "-e";
+		args << "select POSIX file \"" + filename + "\"";
+		args << "-e";
+		args << "end tell";
+
+		QProcess::startDetached("osascript", args);
+#endif
+	}
+}
diff --git a/Grinder/util/FileUtils.h b/Grinder/util/FileUtils.h
index 9898e5269fda75839c859357c5a05b0b4961a216..b248a4e42ac9674cc1f7fbf8c6941c4a176b0383 100644
--- a/Grinder/util/FileUtils.h
+++ b/Grinder/util/FileUtils.h
@@ -20,6 +20,8 @@ namespace grndr
 
 		static QString getTemporaryFileName(QString filename);
 
+		static void openInFileBrowser(QString filename);
+
 	private:
 		FileUtils();
 	};
diff --git a/Grinder/util/MathUtils.h b/Grinder/util/MathUtils.h
index f637a67995fd134e74ceb14bf67bbf75166a0c47..244d0da32d6126a5ab68146a24910a4aac6a7b0f 100644
--- a/Grinder/util/MathUtils.h
+++ b/Grinder/util/MathUtils.h
@@ -6,6 +6,8 @@
 #ifndef MATHUTILS_H
 #define MATHUTILS_H
 
+#include <vector>
+
 #include <QPoint>
 #include <QLine>
 
@@ -25,9 +27,14 @@ namespace grndr
 		static QPoint ceil(QPointF pos);
 		static QPoint floor(QPointF pos);
 
+		template<typename T>
+		static std::vector<T> uniqueRandomNumbers(unsigned int count, T min, T max);
+
 	private:
 		MathUtils() { }
 	};
 }
 
+#include "MathUtils.impl.h"
+
 #endif
diff --git a/Grinder/util/MathUtils.impl.h b/Grinder/util/MathUtils.impl.h
new file mode 100644
index 0000000000000000000000000000000000000000..b4fe9cb3b1f4e79d643d91142f7166fcea4e9fe2
--- /dev/null
+++ b/Grinder/util/MathUtils.impl.h
@@ -0,0 +1,26 @@
+/******************************************************************************
+ * File: MathUtils.impl.h
+ * Date: 30.12.2019
+ *****************************************************************************/
+
+#include "Grinder.h"
+#include "MathUtils.h"
+
+template<typename T>
+std::vector<T> MathUtils::uniqueRandomNumbers(unsigned int count, T min, T max)
+{
+	std::vector<T> numbers;
+
+	// Add all numbers from min to max to an array and shuffle it
+	for (T i = min; i <= max; ++i)
+		numbers.push_back(i);
+
+	unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
+	std::shuffle(numbers.begin(), numbers.end(), std::default_random_engine{seed});
+
+	if (count > numbers.size())
+		count = numbers.size();
+
+	// Return the first count items of the shuffled array
+	return std::vector<T>{numbers.begin(), numbers.begin() + count};
+}