From 6e4b0ca11c79f29cc55bb8a1c147a87c53bafdd1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20M=C3=BCller?= <d_muel20@uni-muenster.de>
Date: Sat, 7 Jul 2018 17:00:11 +0200
Subject: [PATCH] * Layers got some flags (locked, renderable, prevent updates)
 * The Output block now has an out port which provides the rendered image for
 further processing * Some data conversion fixes

---
 Grinder/Grinder.pro                           |   6 +-
 Grinder/Version.h                             |   4 +-
 Grinder/common/ObjectVector.h                 |   4 +-
 Grinder/common/ObjectVector.impl.h            |  14 +++++
 Grinder/common/properties/PropertyBase.h      |   4 +-
 Grinder/common/properties/PropertyObject.cpp  |   2 +-
 Grinder/controller/ImageEditorController.cpp  |   8 +--
 Grinder/cv/CVUtils.cpp                        |  11 ++++
 Grinder/cv/CVUtils.h                          |   1 +
 Grinder/cv/Pixel.h                            |   2 +-
 Grinder/engine/data/DataBlob.cpp              |  34 ++++++----
 Grinder/engine/processors/OutputProcessor.cpp |   6 +-
 Grinder/image/ImageBuild.cpp                  |  30 +++++++--
 Grinder/image/ImageBuild.h                    |   7 ++-
 Grinder/image/Layer.cpp                       |  37 ++++++++++-
 Grinder/image/Layer.h                         |  30 ++++++++-
 Grinder/image/LayerItem.h                     |   5 ++
 Grinder/image/LayerPixels.h                   |   5 ++
 Grinder/image/LayerPixelsData.cpp             |  25 ++++++++
 Grinder/image/LayerPixelsData.h               |   7 ++-
 Grinder/pipeline/blocks/OutputBlock.cpp       |   2 +
 Grinder/pipeline/blocks/OutputBlock.h         |   3 +
 Grinder/res/Grinder.qrc                       |   3 +
 Grinder/res/Resources.h                       |   3 +
 Grinder/res/icons/frame-landscape.png         | Bin 0 -> 456 bytes
 Grinder/res/icons/lock.png                    | Bin 0 -> 466 bytes
 Grinder/res/icons/prevent_updates.png         | Bin 0 -> 613 bytes
 .../ui/image/ImageEditorPropertyWidget.cpp    |   2 +-
 Grinder/ui/image/LayersListItem.cpp           |  23 ++++++-
 Grinder/ui/image/LayersListItem.h             |   2 +
 Grinder/ui/image/LayersListItemDelegate.cpp   |  58 ++++++++++++++++++
 Grinder/ui/image/LayersListItemDelegate.h     |  39 ++++++++++++
 Grinder/ui/image/LayersListWidget.cpp         |  50 +++++++++++++++
 Grinder/ui/image/LayersListWidget.h           |  14 +++++
 .../properties/PropertyTreeItemDelegate.cpp   |   1 -
 .../ui/properties/ValuePropertyTreeItem.cpp   |  10 +--
 Grinder/ui/widgets/ActiveObjectListItem.h     |   2 +-
 Grinder/ui/widgets/ObjectListWidget.h         |   4 +-
 Grinder/ui/widgets/ObjectListWidget.impl.h    |   9 ++-
 39 files changed, 415 insertions(+), 52 deletions(-)
 create mode 100644 Grinder/res/icons/frame-landscape.png
 create mode 100644 Grinder/res/icons/lock.png
 create mode 100644 Grinder/res/icons/prevent_updates.png
 create mode 100644 Grinder/ui/image/LayersListItemDelegate.cpp
 create mode 100644 Grinder/ui/image/LayersListItemDelegate.h

diff --git a/Grinder/Grinder.pro b/Grinder/Grinder.pro
index dd5c243..ef84ae8 100644
--- a/Grinder/Grinder.pro
+++ b/Grinder/Grinder.pro
@@ -282,7 +282,8 @@ SOURCES += \
     image/draftitems/EllipseDraftItem.cpp \
     image/renderers/EllipseDraftItemRenderer.cpp \
     ui/image/draftitems/EllipseDraftItemNode.cpp \
-    ui/image/tools/EllipseDraftItemTool.cpp
+    ui/image/tools/EllipseDraftItemTool.cpp \
+    ui/image/LayersListItemDelegate.cpp
 
 HEADERS += \
 	ui/mainwnd/GrinderWindow.h \
@@ -613,7 +614,8 @@ HEADERS += \
     image/draftitems/EllipseDraftItem.h \
     image/renderers/EllipseDraftItemRenderer.h \
     ui/image/draftitems/EllipseDraftItemNode.h \
-    ui/image/tools/EllipseDraftItemTool.h
+    ui/image/tools/EllipseDraftItemTool.h \
+    ui/image/LayersListItemDelegate.h
 
 FORMS += \
 	ui/mainwnd/GrinderWindow.ui \
diff --git a/Grinder/Version.h b/Grinder/Version.h
index efbbbe9..1f3eb58 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			"03.07.2018"
+#define GRNDR_INFO_DATE			"07.07.2018"
 #define GRNDR_INFO_COMPANY		"WWU Muenster"
 #define GRNDR_INFO_WEBSITE		"http://www.uni-muenster.de"
 
 #define GRNDR_VERSION_MAJOR		0
 #define GRNDR_VERSION_MINOR		6
 #define GRNDR_VERSION_REVISION	0
-#define GRNDR_VERSION_BUILD		207
+#define GRNDR_VERSION_BUILD		210
 
 namespace grndr
 {
diff --git a/Grinder/common/ObjectVector.h b/Grinder/common/ObjectVector.h
index 8d004e1..41d9f54 100644
--- a/Grinder/common/ObjectVector.h
+++ b/Grinder/common/ObjectVector.h
@@ -20,7 +20,7 @@ namespace grndr
 	public:
 		using vector_type = ObjectVector<ObjType>;
 		using object_type = ObjType;
-		using object_vec_type = std::vector<ObjType>;
+		using object_vec_type = std::vector<ObjType*>;
 		using pointer_type = std::shared_ptr<ObjType>;
 		using pointer_vec_type = std::vector<pointer_type>;
 		using size_type = typename std::vector<std::shared_ptr<ObjType>>::size_type;
@@ -46,6 +46,8 @@ namespace grndr
 		pointer_type selectFirst(std::function<bool(const pointer_type&)> pred) const;
 		pointer_vec_type select(std::function<bool(const pointer_type&)> pred) const;
 
+		object_vec_type toVector(std::function<bool(const pointer_type&)> pred = nullptr) const;
+
 		auto find(const pointer_type& obj) const;
 		auto find(const object_type* obj) const;
 		auto find(std::function<bool(const pointer_type&)> pred) const;
diff --git a/Grinder/common/ObjectVector.impl.h b/Grinder/common/ObjectVector.impl.h
index cbab4fa..2caea59 100644
--- a/Grinder/common/ObjectVector.impl.h
+++ b/Grinder/common/ObjectVector.impl.h
@@ -63,6 +63,20 @@ typename ObjectVector<ObjType>::pointer_vec_type ObjectVector<ObjType>::select(s
 	return objects;
 }
 
+template<typename ObjType>
+typename ObjectVector<ObjType>::object_vec_type ObjectVector<ObjType>::toVector(std::function<bool(const pointer_type&)> pred) const
+{
+	object_vec_type objects;
+
+	for (auto obj : *this)
+	{
+		if (!pred || pred(obj))
+			objects.push_back(obj.get());
+	}
+
+	return objects;
+}
+
 template<typename ObjType>
 auto ObjectVector<ObjType>::find(const pointer_type& obj) const
 {
diff --git a/Grinder/common/properties/PropertyBase.h b/Grinder/common/properties/PropertyBase.h
index ee796f5..5318a4a 100644
--- a/Grinder/common/properties/PropertyBase.h
+++ b/Grinder/common/properties/PropertyBase.h
@@ -26,7 +26,6 @@ namespace grndr
 			None = 0x0000,
 			ReadOnly = 0x0001,
 			Hidden = 0x0002,
-			Disabled = 0x0004,
 		};
 
 		Q_DECLARE_FLAGS(Flags, Flag)
@@ -59,6 +58,8 @@ namespace grndr
 		void setFlag(Flag flag, bool set = true) { _flags.setFlag(flag, set); }
 		Flags getFlags() const { return _flags; }
 		void setFlags(Flags flags) { _flags = flags; }
+		bool isEnabled() const { return _isEnabled; }
+		void setEnabled(bool enable = true) { _isEnabled = enable; }
 
 	public:
 		virtual QWidget* createEditor(QWidget* parent = nullptr) { Q_UNUSED(parent); return nullptr; }
@@ -82,6 +83,7 @@ namespace grndr
 		QString _group;
 
 		Flags _flags{Flag::None};
+		bool _isEnabled{true};
 	};	
 }
 
diff --git a/Grinder/common/properties/PropertyObject.cpp b/Grinder/common/properties/PropertyObject.cpp
index 797cfbe..c00ebe2 100644
--- a/Grinder/common/properties/PropertyObject.cpp
+++ b/Grinder/common/properties/PropertyObject.cpp
@@ -81,7 +81,7 @@ bool PropertyObject::enableDependantProperty(PropertyBase* updatedProp, std::sha
 {
 	if (!updatedProp || updatedProp == superordinateProp.get())
 	{
-		dependantProp->setFlag(PropertyBase::Flag::Disabled, !condition());
+		dependantProp->setEnabled(condition());
 		return true;
 	}
 	else
diff --git a/Grinder/controller/ImageEditorController.cpp b/Grinder/controller/ImageEditorController.cpp
index 4def290..edb542d 100644
--- a/Grinder/controller/ImageEditorController.cpp
+++ b/Grinder/controller/ImageEditorController.cpp
@@ -196,7 +196,7 @@ std::shared_ptr<Layer> ImageEditorController::createLayer(QString name) const
 
 void ImageEditorController::copyLayer(const Layer* layer) const
 {
-	callControllerFunction("Copying a layer", [this](const Layer* layer) {
+	callControllerFunction("Copying a layer", [](const Layer* layer) {
 		grinder()->clipboardManager().serialize<Layer>(LayerVector::Serialization_Element, {layer});
 		return true;
 	}, layer);
@@ -277,7 +277,7 @@ std::shared_ptr<DraftItem> ImageEditorController::createDraftItem(DraftItemType
 
 	if (layer)
 	{
-		return callControllerFunction("Creating a draft item", [this](DraftItemType type, Layer* layer) {
+		return callControllerFunction("Creating a draft item", [](DraftItemType type, Layer* layer) {
 			return layer->createDraftItem(type);
 		}, type, layer);
 	}
@@ -291,7 +291,7 @@ void ImageEditorController::removeDraftItem(const DraftItem* item) const
 
 	if (item)
 	{
-		callControllerFunction("Removing a draft item", [this](const DraftItem* item, Layer* layer) {
+		callControllerFunction("Removing a draft item", [](const DraftItem* item, Layer* layer) {
 			layer->removeDraftItem(item);
 			return true;
 		}, item, layer);
@@ -656,7 +656,7 @@ void ImageEditorController::connectLayerSignals(Layer* layer, bool connectSignal
 		connect(layer, &Layer::draftItemRemoved, this, &ImageEditorController::draftItemRemoved);
 		connect(layer, &Layer::layerShown, this, &ImageEditorController::layerVisibilityChanged);
 		connect(layer, &Layer::layerHidden, this, &ImageEditorController::layerVisibilityChanged);
-		connect(layer, &Layer::layerAlphaChanged, this, &ImageEditorController::layerAlphaChanged);
+		connect(layer, &Layer::alphaChanged, this, &ImageEditorController::layerAlphaChanged);
 	}
 	else
 		disconnect(layer, nullptr, this, nullptr);
diff --git a/Grinder/cv/CVUtils.cpp b/Grinder/cv/CVUtils.cpp
index a871cec..0f1b214 100644
--- a/Grinder/cv/CVUtils.cpp
+++ b/Grinder/cv/CVUtils.cpp
@@ -76,3 +76,14 @@ std::vector<QColor> CVUtils::generateColors(unsigned int count, float saturation
 
 	return colors;
 }
+
+QColor CVUtils::mixColors(QColor clr1, QColor clr2, float alpha)
+{
+	auto red = (1 - alpha) * clr1.redF() + alpha * clr2.redF();
+	auto green = (1 - alpha) * clr1.greenF() + alpha * clr2.greenF();
+	auto blue = (1 - alpha) * clr1.blueF() + alpha * clr2.blueF();
+
+	QColor color;
+	color.setRgbF(red, green, blue);
+	return color;
+}
diff --git a/Grinder/cv/CVUtils.h b/Grinder/cv/CVUtils.h
index aa336df..e300691 100644
--- a/Grinder/cv/CVUtils.h
+++ b/Grinder/cv/CVUtils.h
@@ -27,6 +27,7 @@ namespace grndr
 		static QColor colorToGrayscale(QColor color);
 		static bool compareColors(QColor clr1, QColor clr2, float tolerance = 0.0f);
 		static std::vector<QColor> generateColors(unsigned int count, float saturation = 1.0, float value = 1.0);
+		static QColor mixColors(QColor clr1, QColor clr2, float alpha);
 
 	private:
 		CVUtils() { }
diff --git a/Grinder/cv/Pixel.h b/Grinder/cv/Pixel.h
index e96bf09..2d4210c 100644
--- a/Grinder/cv/Pixel.h
+++ b/Grinder/cv/Pixel.h
@@ -95,7 +95,7 @@ namespace grndr
 
 	public:
 		PixelRef at(int r, int c);
-		PixelRef at(QPoint pos) { return at(pos.x(), pos.y()); }
+		PixelRef at(QPoint pos) { return at(pos.y(), pos.x()); }
 		void forEach(std::function<void(PixelRef, QPoint)> callback);
 
 		PixelRef operator [](QPoint pos) { return at(pos); }
diff --git a/Grinder/engine/data/DataBlob.cpp b/Grinder/engine/data/DataBlob.cpp
index b75e33d..cbbcf1b 100644
--- a/Grinder/engine/data/DataBlob.cpp
+++ b/Grinder/engine/data/DataBlob.cpp
@@ -44,21 +44,15 @@ void DataBlob::convertTo(const DataDescriptor& dataDesc, bool normalize, double
 	dataDesc.getCVMatrixType(&channels, &depth);
 
 	try {
-		// First, convert the value type if necessary
-		if (dataDesc.getValueType() != DataDescriptor::ValueType::Any && depth != _data.depth())
+		// First, convert image colors count if necessary
+		if (dataDesc.getFieldType() != DataDescriptor::FieldType::Any && channels != _data.channels())
 		{
-			if (normalize && dataDesc.getValueType() < _dataDescriptor.getValueType())	// Normalize only if the new type is smaller than the current one
-				cv::normalize(_data, _data, minValue, maxValue, cv::NORM_MINMAX);
-
-			_data.convertTo(_data, depth);
+			// Color conversion can only be carried out on 8- or 16-bit unsigned or floating-point images
+			auto depth = _data.depth();
 
-			// Update the new data descriptor to match the new value type
-			dataDescNew = DataDescriptor{dataDescNew.getName(), dataDescNew.getStructureType(), dataDescNew.getFieldType(), dataDesc.getValueType()};
-		}
+			if (depth != CV_8U && depth != CV_16U && depth != CV_32F)
+				_data.convertTo(_data, CV_32F);
 
-		// Next, convert image colors count if necessary
-		if (dataDesc.getFieldType() != DataDescriptor::FieldType::Any && channels != _data.channels())
-		{
 			// Check if a color <-> grayscale conversion can be done
 			if (_dataDescriptor.canConvertToColor(dataDesc))
 			{
@@ -80,6 +74,22 @@ void DataBlob::convertTo(const DataDescriptor& dataDesc, bool normalize, double
 				// Update the new data descriptor to match the new field type
 				dataDescNew = DataDescriptor{dataDescNew.getName(), dataDescNew.getStructureType(), DataDescriptor::FieldType::Basic, dataDescNew.getValueType()};
 			}
+
+			// Convert the data back to the original depth
+			if (_data.depth() != depth)
+				_data.convertTo(_data, depth);
+		}
+
+		// Next, convert the value type if necessary
+		if (dataDesc.getValueType() != DataDescriptor::ValueType::Any && depth != _data.depth())
+		{
+			if (normalize && dataDesc.getValueType() < _dataDescriptor.getValueType())	// Normalize only if the new type is smaller than the current one
+				cv::normalize(_data, _data, minValue, maxValue, cv::NORM_MINMAX);
+
+			_data.convertTo(_data, depth);
+
+			// Update the new data descriptor to match the new value type
+			dataDescNew = DataDescriptor{dataDescNew.getName(), dataDescNew.getStructureType(), dataDescNew.getFieldType(), dataDesc.getValueType()};
 		}
 
 		// Set the new data descriptor to match the converted type
diff --git a/Grinder/engine/processors/OutputProcessor.cpp b/Grinder/engine/processors/OutputProcessor.cpp
index 64e04ed..1560118 100644
--- a/Grinder/engine/processors/OutputProcessor.cpp
+++ b/Grinder/engine/processors/OutputProcessor.cpp
@@ -31,8 +31,12 @@ void OutputProcessor::execute(EngineExecutionContext& ctx)
 
 			imageBuild->setImageData(processedImage);
 
+			// Render the entire image build to provide it as this block's output
+			cv::Mat renderedImage = imageBuild->renderImageBuild();
+			ctx.setContextEntry(_block->outPort(), DataBlob{getPortDataDescriptor(_block->outPort()), renderedImage});
+
 			if (ctx.getExecutionMode() == Engine::ExecutionMode::View)
-				grinder()->imageEditorManager().showEditor(_block, imageBuild);
+				grinder()->imageEditorManager().showEditor(_block, imageBuild);						
 		}
 	}
 }
diff --git a/Grinder/image/ImageBuild.cpp b/Grinder/image/ImageBuild.cpp
index f3feb68..ef737ba 100644
--- a/Grinder/image/ImageBuild.cpp
+++ b/Grinder/image/ImageBuild.cpp
@@ -10,6 +10,8 @@
 #include "project/ImageReference.h"
 #include "properties/ImageTagsProperty.h"
 
+#include <opencv2/imgproc.hpp>
+
 const char* ImageBuild::Serialization_Value_ImageReferences = "ImageReferences";
 
 ImageBuild::ImageBuild(const Block* block, const std::vector<const ImageReference*>& imageReferences) :
@@ -37,15 +39,15 @@ void ImageBuild::initImageBuild()
 	// Create background image layers for all assigned image references
 	for (const auto imageRef : _imageReferences)
 	{
-		if (auto backgroundLayer = createLayer(QString{"Img: %1"}.arg(imageRef->getImageFileName()), Layer::Type::BackgroundImage, imageRef))
+		if (auto backgroundLayer = createLayer(QString{"Img: %1"}.arg(imageRef->getImageFileName()), Layer::Type::BackgroundImage, Layer::Flag::Locked, imageRef))
 			backgroundLayer->setVisible(false);
 	}
 }
 
-std::shared_ptr<Layer> ImageBuild::createLayer(QString name, Layer::Type layerType, const ImageReference* backgroundImage)
+std::shared_ptr<Layer> ImageBuild::createLayer(QString name, Layer::Type layerType, Layer::Flags flags, const ImageReference* backgroundImage)
 {
 	// Create new layer
-	auto layer = std::make_shared<Layer>(this, name, layerType, backgroundImage);
+	auto layer = std::make_shared<Layer>(this, name, layerType, flags, backgroundImage);
 
 	try {	// Propagate initialization errors to the caller
 		layer->initLayer();
@@ -111,6 +113,25 @@ void ImageBuild::moveLayer(const Layer* layer, bool up)
 		throw ImageBuildException{this, _EXCPT("Tried to move a layer not belonging to this image build")};
 }
 
+cv::Mat ImageBuild::renderImageBuild() const
+{
+	// Make sure that the rendered image is an 8-bit color one
+	cv::Mat renderedImage;
+	_imageData.convertTo(renderedImage, CV_8U);
+
+	if (renderedImage.channels() == 1)
+		cv::cvtColor(renderedImage, renderedImage, cv::COLOR_GRAY2BGR);
+
+	// Render each layer (unless set to be not renderable) onto the resulting image
+	for (const auto& layer : _layers)
+	{
+		if (layer->hasFlag(Layer::Flag::Renderable) && layer->getAlpha() > 0)
+			layer->renderLayer(renderedImage);
+	}
+
+	return renderedImage;
+}
+
 ImageTags* ImageBuild::inputImageTags() const
 {
 	if (auto imageTagsProperty = _block->portProperty<ImageTagsProperty>(PortType::ImageTagsIn, PropertyID::ImageTags))
@@ -163,7 +184,8 @@ void ImageBuild::deserialize(DeserializationContext& ctx)
 			}
 
 			QString name = settings(Layer::Serialization_Value_Name).toString();
-			return createLayer(name, type, backgroundImage);
+			Layer::Flags flags = static_cast<Layer::Flags>(settings(Layer::Serialization_Value_Flags, static_cast<int>(Layer::Flag::Renderable)).toInt());
+			return createLayer(name, type, flags, backgroundImage);
 		});
 
 		ctx.endGroup();
diff --git a/Grinder/image/ImageBuild.h b/Grinder/image/ImageBuild.h
index b9e8057..e6b0666 100644
--- a/Grinder/image/ImageBuild.h
+++ b/Grinder/image/ImageBuild.h
@@ -31,19 +31,22 @@ namespace grndr
 		void initImageBuild();
 
 	public:
-		std::shared_ptr<Layer> createLayer(QString name = "", Layer::Type layerType = Layer::Type::Standard, const ImageReference* backgroundImage = nullptr);
+		std::shared_ptr<Layer> createLayer(QString name = "", Layer::Type layerType = Layer::Type::Standard, Layer::Flags flags = Layer::Flag::Renderable, const ImageReference* backgroundImage = nullptr);
 		void removeLayer(const Layer* layer);
 		void removeAllLayers();
 
 		void moveLayer(const Layer* layer, bool up);
 
+	public:
+		cv::Mat renderImageBuild() const;
+
 	public:
 		const Block* block() const { return _block; }
 		const std::vector<const ImageReference*>& imageReferences() const { return _imageReferences; }
 
 		const cv::Mat& imageData() const { return _imageData; }
 		void setImageData(const cv::Mat& data) { _imageData = data; emit imageDataChanged(); }
-		void clearImageData() { _imageData.release(); emit imageDataChanged(); }
+		void clearImageData() { _imageData.release(); emit imageDataChanged(); }		
 
 		const LayerVector& layers() const { return _layers; }
 
diff --git a/Grinder/image/Layer.cpp b/Grinder/image/Layer.cpp
index e7af27f..a4b6c09 100644
--- a/Grinder/image/Layer.cpp
+++ b/Grinder/image/Layer.cpp
@@ -8,14 +8,16 @@
 #include "ImageBuild.h"
 #include "ImageExceptions.h"
 #include "DraftItemCatalog.h"
+#include "project/ImageReference.h"
 
 const char* Layer::Serialization_Value_Type = "Type";
+const char* Layer::Serialization_Value_Flags = "Flags";
 const char* Layer::Serialization_Value_Visible = "Visible";
 const char* Layer::Serialization_Value_Alpha = "Alpha";
 const char* Layer::Serialization_Value_BackgroundImage = "BackgroundImage";
 
-Layer::Layer(ImageBuild* imageBuild, QString name, Type type, const ImageReference* backgroundImage) : ImageBuildItem(imageBuild, name),
-	_type{type}, _draftItems{this}, _layerPixels{this}, _backgroundImage{backgroundImage}
+Layer::Layer(ImageBuild* imageBuild, QString name, Type type, Flags flags, const ImageReference* backgroundImage) : ImageBuildItem(imageBuild, name),
+	_type{type}, _flags{flags}, _draftItems{this}, _layerPixels{this}, _backgroundImage{backgroundImage}
 {
 	if (type == Type::Standard)	// Standard layers don't have a background
 		_backgroundImage = nullptr;
@@ -67,6 +69,33 @@ void Layer::removeDraftItem(const DraftItem* item)
 	}
 }
 
+void Layer::renderLayer(cv::Mat& image) const
+{
+	LayerPixels renderedPixels = _layerPixels;
+
+	// Render all draft items using a default renderer style
+	renderedPixels.renderDraftItems(_draftItems.toVector(), ItemRenderer::RendererStyle{});
+
+	// Render the background image
+	if (_backgroundImage)
+	{
+		auto backgroundImage = _backgroundImage->loadImage();
+
+		if (image.size() == backgroundImage.size())	// Only do this if both images are of the same size
+		{
+			float alpha = _alpha / 100.0f;
+
+			if (alpha >= 1.0f)
+				image = backgroundImage;
+			else
+				cv::addWeighted(image, 1 - alpha, backgroundImage, alpha, 0.0, image);
+		}
+	}
+
+	// Render the layer pixels
+	renderedPixels.data().renderPixels(image, _alpha / 100.0f);
+}
+
 int Layer::getZOrder() const
 {
 	return _imageBuild->layers().indexOf(this);
@@ -93,7 +122,7 @@ void Layer::setAlpha(unsigned int alpha)
 	if (alpha != _alpha)
 	{
 		_alpha = alpha;
-		emit layerAlphaChanged(_alpha);
+		emit alphaChanged(_alpha);
 	}
 }
 
@@ -103,6 +132,7 @@ void Layer::serialize(SerializationContext& ctx) const
 
 	// Serialize values
 	ctx.settings()(Serialization_Value_Type) = static_cast<int>(_type);
+	ctx.settings()(Serialization_Value_Flags) = static_cast<int>(_flags);
 	ctx.settings()(Serialization_Value_Visible) = _isVisible;
 	ctx.settings()(Serialization_Value_Alpha) = _alpha;
 	ctx.settings()(Serialization_Value_BackgroundImage) = _backgroundImage ? ctx.getImageReferenceIndex(_backgroundImage) : -1;
@@ -124,6 +154,7 @@ void Layer::deserialize(DeserializationContext& ctx)
 
 	// Deserialize values
 	_type = static_cast<Type>(ctx.settings()(Serialization_Value_Type, static_cast<int>(Type::Standard)).toInt());
+	_flags = static_cast<Flags>(ctx.settings()(Serialization_Value_Flags, static_cast<int>(Flag::Renderable)).toInt());
 	_alpha = ctx.settings()(Serialization_Value_Alpha, 100).toUInt();
 	_isVisible = ctx.settings()(Serialization_Value_Visible, true).toBool();
 
diff --git a/Grinder/image/Layer.h b/Grinder/image/Layer.h
index d4c8b16..bbae02a 100644
--- a/Grinder/image/Layer.h
+++ b/Grinder/image/Layer.h
@@ -6,6 +6,8 @@
 #ifndef LAYER_H
 #define LAYER_H
 
+#include <opencv2/core.hpp>
+
 #include "ImageBuildItem.h"
 #include "DraftItemVector.h"
 #include "LayerPixels.h"
@@ -18,6 +20,7 @@ namespace grndr
 
 	public:
 		static const char* Serialization_Value_Type;
+		static const char* Serialization_Value_Flags;
 		static const char* Serialization_Value_Visible;
 		static const char* Serialization_Value_Alpha;
 		static const char* Serialization_Value_BackgroundImage;
@@ -28,8 +31,18 @@ namespace grndr
 			BackgroundImage,
 		};
 
+		enum class Flag : unsigned int
+		{
+			None = 0x0000,
+			Locked = 0x0001,
+			Renderable = 0x0002,
+			PreventUpdates = 0x0004,
+		};
+
+		Q_DECLARE_FLAGS(Flags, Flag)
+
 	public:
-		Layer(ImageBuild* imageBuild, QString name = "", Type type = Type::Standard, const ImageReference* backgroundImage = nullptr);
+		Layer(ImageBuild* imageBuild, QString name = "", Type type = Type::Standard, Flags flags = Flag::Renderable, const ImageReference* backgroundImage = nullptr);
 
 	public:
 		void initLayer();
@@ -38,11 +51,18 @@ namespace grndr
 		std::shared_ptr<DraftItem> createDraftItem(DraftItemType type);
 		void removeDraftItem(const DraftItem* item);
 
+	public:
+		void renderLayer(cv::Mat& image) const;
+
 	public:
 		Type getType() const { return _type; }
 
 		int getZOrder() const;
 
+		bool hasFlag(Flag flag) const { return _flags.testFlag(flag); }
+		void setFlag(Flag flag, bool set = true) { _flags.setFlag(flag, set); emit flagsChanged(_flags); }
+		Flags getFlags() const { return _flags; }
+		void setFlags(Flags flags) { _flags = flags; emit flagsChanged(_flags); }
 		bool isVisible() const { return _isVisible; }
 		void setVisible(bool visible = true);
 		unsigned int getAlpha() const { return _alpha; }
@@ -62,9 +82,10 @@ namespace grndr
 		virtual void deserialize(DeserializationContext& ctx) override;
 
 	signals:
+		void flagsChanged(Flags);
 		void layerShown();
 		void layerHidden();
-		void layerAlphaChanged(unsigned int);
+		void alphaChanged(unsigned int);
 
 		void draftItemCreated(const std::shared_ptr<DraftItem>&);
 		void draftItemRemoved(const std::shared_ptr<DraftItem>&);
@@ -72,14 +93,17 @@ namespace grndr
 	private:
 		Type _type{Type::Standard};
 
+		Flags _flags;
 		bool _isVisible{true};
 		unsigned int _alpha{100};
 
 		DraftItemVector _draftItems;
-		LayerPixels _layerPixels;
+		LayerPixels _layerPixels;		
 
 		const ImageReference* _backgroundImage{nullptr};
 	};
 }
 
+Q_DECLARE_OPERATORS_FOR_FLAGS(grndr::Layer::Flags)
+
 #endif
diff --git a/Grinder/image/LayerItem.h b/Grinder/image/LayerItem.h
index e1d066b..d33364f 100644
--- a/Grinder/image/LayerItem.h
+++ b/Grinder/image/LayerItem.h
@@ -20,6 +20,11 @@ namespace grndr
 
 	public:
 		LayerItem(Layer* layer);
+		LayerItem(const LayerItem& other) : QObject{}, _layer{other._layer} { }
+		LayerItem(LayerItem&& other) : QObject{}, _layer{other._layer} { }
+
+		LayerItem& operator =(const LayerItem& other) { _layer = other._layer; return *this; }
+		LayerItem& operator =(LayerItem&& other) { _layer = other._layer; return *this; }
 
 	public:
 		virtual std::unique_ptr<ItemRenderer> createRenderer(const ItemRenderer::RendererStyle& rendererStyle) const = 0;
diff --git a/Grinder/image/LayerPixels.h b/Grinder/image/LayerPixels.h
index d7ca851..0d62e27 100644
--- a/Grinder/image/LayerPixels.h
+++ b/Grinder/image/LayerPixels.h
@@ -25,6 +25,11 @@ namespace grndr
 
 	public:
 		LayerPixels(Layer* layer);
+		LayerPixels(const LayerPixels& other) = default;
+		LayerPixels(LayerPixels&& other) = default;
+
+		LayerPixels& operator =(const LayerPixels& other) = default;
+		LayerPixels& operator =(LayerPixels&& other) = default;
 
 	public:
 		void renderDraftItems(const std::vector<DraftItem*>& draftItems, const ItemRenderer::RendererStyle& rendererStyle);
diff --git a/Grinder/image/LayerPixelsData.cpp b/Grinder/image/LayerPixelsData.cpp
index ea96c99..1f2a084 100644
--- a/Grinder/image/LayerPixelsData.cpp
+++ b/Grinder/image/LayerPixelsData.cpp
@@ -6,6 +6,8 @@
 #include "Grinder.h"
 #include "LayerPixelsData.h"
 #include "image/ImageUtils.h"
+#include "cv/Pixel.h"
+#include "cv/CVUtils.h"
 
 const char* LayerPixelsData::Serialization_Group = "PixelsData";
 
@@ -95,6 +97,29 @@ void LayerPixelsData::drawImage(const QImage& image)
 	endPainting();
 }
 
+void LayerPixelsData::renderPixels(cv::Mat& image, float alpha) const
+{
+	PixelAccessor pixels{image};
+
+	for (auto it = _colorMap.begin(); it != _colorMap.end(); ++it)
+	{
+		for (const auto& rangeMap : it->second.rangeMap())
+		{
+			int yPos = rangeMap.first;
+
+			for (const auto& xPos : rangeMap.second)
+			{
+				QPoint pos{std::get<1>(xPos), yPos};
+
+				if (alpha >= 1.0f)
+					pixels.at(pos) = it->first;
+				else
+					pixels.at(pos) = CVUtils::mixColors(pixels.at(pos), it->first, alpha);
+			}
+		}
+	}
+}
+
 std::vector<QColor> LayerPixelsData::getColors() const
 {
 	std::vector<QColor> colors;
diff --git a/Grinder/image/LayerPixelsData.h b/Grinder/image/LayerPixelsData.h
index e45b095..0125554 100644
--- a/Grinder/image/LayerPixelsData.h
+++ b/Grinder/image/LayerPixelsData.h
@@ -6,6 +6,8 @@
 #ifndef LAYERPIXELSDATA_H
 #define LAYERPIXELSDATA_H
 
+#include <opencv2/core.hpp>
+
 #include "common/RangeMap.h"
 #include "common/CommonOperators.h"
 #include "common/serialization/SerializationContext.h"
@@ -38,7 +40,7 @@ namespace grndr
 
 	public:
 		QColor get(int x, int y) const;
-		QColor get(QPoint pos) { return get(pos.x(), pos.y()); }
+		QColor get(QPoint pos) const { return get(pos.x(), pos.y()); }
 		void set(const range_map_type& rangeMap, QColor color) { _colorMap[color] = rangeMap; emit dataModified(); }
 		void set(range_map_type&& rangeMap, QColor color) { _colorMap[color] = std::move(rangeMap); emit dataModified(); }
 		void set(const range_type& range, int y, QColor color);
@@ -57,6 +59,9 @@ namespace grndr
 		void beginPainting() { blockSignals(true); }
 		void endPainting() { blockSignals(false); emit dataModified(); }
 
+	public:
+		void renderPixels(cv::Mat& image, float alpha) const;
+
 	public:
 		const range_map_type& at(QColor at) const { return _colorMap.at(at); }
 		const range_map_type& operator [](QColor at) const { return _colorMap.at(at); }
diff --git a/Grinder/pipeline/blocks/OutputBlock.cpp b/Grinder/pipeline/blocks/OutputBlock.cpp
index b43bce4..bed0980 100644
--- a/Grinder/pipeline/blocks/OutputBlock.cpp
+++ b/Grinder/pipeline/blocks/OutputBlock.cpp
@@ -39,4 +39,6 @@ void OutputBlock::createPorts()
 	_inPort = createPort(PortType::ImageIn, Port::Direction::In, inPortDataDescs, "In");
 
 	_imageTagsPort = createPort(PortType::ImageTagsIn, Port::Direction::In, {}, "Tags");
+
+	_outPort = createPort(PortType::ImageOut, Port::Direction::Out, {DataDescriptor::imageDescriptor()}, "Out");
 }
diff --git a/Grinder/pipeline/blocks/OutputBlock.h b/Grinder/pipeline/blocks/OutputBlock.h
index fb1b571..b2aea5d 100644
--- a/Grinder/pipeline/blocks/OutputBlock.h
+++ b/Grinder/pipeline/blocks/OutputBlock.h
@@ -34,6 +34,8 @@ namespace grndr
 		const Port* inPort() const { return _inPort.get(); }
 		Port* imageTagsPort() { return _imageTagsPort.get(); }
 		const Port* imageTagsPort() const { return _imageTagsPort.get(); }
+		Port* outPort() { return _outPort.get(); }
+		const Port* outPort() const { return _outPort.get(); }
 
 	protected:
 		virtual void createProperties() override;
@@ -45,6 +47,7 @@ namespace grndr
 
 		std::shared_ptr<Port> _inPort;
 		std::shared_ptr<Port> _imageTagsPort;
+		std::shared_ptr<Port> _outPort;
 	};
 }
 
diff --git a/Grinder/res/Grinder.qrc b/Grinder/res/Grinder.qrc
index cf804bd..efd926c 100644
--- a/Grinder/res/Grinder.qrc
+++ b/Grinder/res/Grinder.qrc
@@ -50,6 +50,9 @@
         <file>icons/zoom.png</file>
         <file>icons/painting/fill.png</file>
         <file>icons/painting/Circle.png</file>
+        <file>icons/lock.png</file>
+        <file>icons/frame-landscape.png</file>
+        <file>icons/prevent_updates.png</file>
     </qresource>
     <qresource prefix="/">
         <file>css/global.css</file>
diff --git a/Grinder/res/Resources.h b/Grinder/res/Resources.h
index 263617e..69d42fe 100644
--- a/Grinder/res/Resources.h
+++ b/Grinder/res/Resources.h
@@ -36,6 +36,9 @@
 #define FILE_ICON_CUT ":/icons/icons/cut.png"
 #define FILE_ICON_CHECK ":/icons/icons/check-mark-black-outline.png"
 #define FILE_ICON_CHECKALL ":/icons/icons/multi_checks.png"
+#define FILE_ICON_LOCKED ":/icons/icons/lock.png"
+#define FILE_ICON_RENDERABLE ":/icons/icons/frame-landscape.png"
+#define FILE_ICON_PREVENTUPDATES ":/icons/icons/prevent_updates.png"
 
 #define FILE_ICON_MOVEUP ":/icons/icons/arrow-up.png"
 #define FILE_ICON_MOVEDOWN ":/icons/icons/arrow-down.png"
diff --git a/Grinder/res/icons/frame-landscape.png b/Grinder/res/icons/frame-landscape.png
new file mode 100644
index 0000000000000000000000000000000000000000..f4afb16c936f68284449527e788052918adf3f5c
GIT binary patch
literal 456
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Ea{HEjtmSN`?>!lvI6-E$sR$z
z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8X6a5m^j0`zi=C+FpzJ
z2oz*5@$_|Nf5grrXe_qf(a8fSBw6AbQ4*Y=R#Ki=l*&+$n3-3imzP?iV4`QBXJq(M
zA#*AN10$QKi(`m|fARtekJBgEPMtc{I-%LIO7f8M;{U--3)x(BxqExID6%$xIl$Y|
z(etkD7l$?nZ|_v@?v7bva&m9kM3Wd^D&A_?aN<R$LTRGl#qxztKX_NHU!UG&bWlOX
z=#t!vCTWMQR|6F9u(`M$yVc_A_SV<iJGqrBku`$1qo+rN@zxg!6}FOhCL6poIQaR)
zJNo*bH5YA^Iv^n@xK;E>`{NZWOeQ?q@FF94!9o4Q%8UAs963^uY^q>mv`NI(nB_l9
z)*8k~`UM+1UeqcoD$dbhWB6OcvEqezj36UV$bQ)wd}@t15|~dMT=;OJy@Sb3wgU#v
s9xf#bE-ob;mJ&<EMHYxKu(2^r>el_!9{2A$FxVJ8UHx3vIVCg!0A4JemH+?%

literal 0
HcmV?d00001

diff --git a/Grinder/res/icons/lock.png b/Grinder/res/icons/lock.png
new file mode 100644
index 0000000000000000000000000000000000000000..2ad34a8f6bb572c2b01e38a827b6ed251ceda8ee
GIT binary patch
literal 466
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Ea{HEjtmSN`?>!lvI6-E$sR$z
z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8X6a5m^j0`zi=C+FpzJ
z2oz*5@$_|Nf5grrXsEZgbVdkJNV3E=q9iy!t)x7$D3zfgF*C13FE6!3!9>qM&&cqj
zLgrKk21Wr-7sn6_|K3TqS%)12T3OrX9Qdf(#JtnvK!xhrbF0c8ADenE$$+J(`_4hv
zSt5SIi!SX5e6A!>J+JIS{TaK8il5KUPEIkC5*D;w`sAMQ>H7xf60G*=JHInkv|D~H
z_BPvLpXA3;DK~u%rEYcklQ!eI_oDPUTUWTaJ$6=oVpeyvR$~=wo1{@!+a&c|L8V7q
z7eyuPo3kCreYlRxaJ|S?rprxp7p)6A-TEg`{Qs=0@8fUZl@#A~&12u&7Ke_BSGT$?
zW_@C({k<ZfF=oMfi#~OOb8{mvNDIiP{A4_~)0SIX;Pltp#@{wO4n$d-9<%vYS<qPX
zTTbr7M<%;(&Hp7MW3yEZrgMK%e!Kts8{;{@*J|uyd={^F?#P|VTEGBg@O1TaS?83{
F1OWN4xYhsw

literal 0
HcmV?d00001

diff --git a/Grinder/res/icons/prevent_updates.png b/Grinder/res/icons/prevent_updates.png
new file mode 100644
index 0000000000000000000000000000000000000000..807a2a13860f4f9037eebdd84d8603ae8cd4be18
GIT binary patch
literal 613
zcmV-r0-F7aP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004XF*Lt006O%
z3;baP00009a7bBm000jK000jK0XoY>h5!Hn8FWQhbW?9;ba!ELWdLwtX>N2bZe?^J
zG%heMGBNQWX_Wu~0pdwSK~y+TZBso@R6!ISq+1i^Pf%l`b{1OMJo6G@X70VS(Qf|$
zy(=}bA{2ZsL^@hyV@xcp2qZR!fR;j`r3Iy&GjHCqADm>8dFP({b#~TGB82obifsq6
z3!LB0Nf((ztjMO(?RM`6(E-FKK~+*p4@$`K9)b#roghA-wuBmxh?2hfsPFpy{=Gs^
zfw2&6fO9og1mo?Y`l?rbhVHL%?}Iz=qMwT-BgXVRs{KKHA4T>vDI!lew)cJ(W4;P%
zU!tb}Jh=bLB4C(o_99Qhb?8ZmY0Haf(Yc^$t1Zw@Fq)i206n3x4wcT){b3<r=47!$
z9+<j0nhfm<xHW2-gYTctN4q3JV6P7#uj~5FocHz!red1g&UsbQ(<}x;)T^-q2p*&J
zci!0<-Tr#9jYZY@s;W9D_76>tvKYt(##U%1ct1Ke8f<`D=je?u5~v-nz&*XBp^1}*
zA^xc~6%9Uun#nv&#B|%vn?1(Zr~Dc3bX)AKEs%{3-34mt2cJMAQrkuKgUMt9{27XT
zMe<&Z`gS>*;d_<eMdDo5>vcf>fQdV-Ug#+>#^=sw&zwRxsH$Ekci}qlq^|3^5^}ur
zv2ko_s4bys&=bB+OmxnlsX6#-gIj8M9>TdBekl<K7EQSE00000NkvXXu0mjfVf+x*

literal 0
HcmV?d00001

diff --git a/Grinder/ui/image/ImageEditorPropertyWidget.cpp b/Grinder/ui/image/ImageEditorPropertyWidget.cpp
index e0b5720..f6f3bdb 100644
--- a/Grinder/ui/image/ImageEditorPropertyWidget.cpp
+++ b/Grinder/ui/image/ImageEditorPropertyWidget.cpp
@@ -120,7 +120,7 @@ void ImageEditorPropertyWidget::addProperty(const std::shared_ptr<PropertyBase>&
 	_layout->addWidget(label, row, 0);
 	_layout->addWidget(editor, row, 1);
 
-	if (property->hasFlag(PropertyBase::Flag::Disabled))
+	if (!property->isEnabled())
 		editor->setEnabled(false);
 }
 
diff --git a/Grinder/ui/image/LayersListItem.cpp b/Grinder/ui/image/LayersListItem.cpp
index eef43f9..ae330fb 100644
--- a/Grinder/ui/image/LayersListItem.cpp
+++ b/Grinder/ui/image/LayersListItem.cpp
@@ -26,11 +26,30 @@ void LayersListItem::updateItem()
 	if (getType() != Layer::Type::Standard)
 	{
 		if (_isActive)
-			this->setFont(_specialActiveFont);
+			setFont(_specialActiveFont);
 		else
-			this->setFont(_specialFont);
+			setFont(_specialFont);
 	}
 
 	setText(getName());
 	setCheckState(isVisible() ? Qt::Checked : Qt::Unchecked);
+
+	// Update the item's tool tip
+	QStringList itemFlags;
+
+	if (hasFlag(Layer::Flag::Locked))
+		itemFlags << "Locked";
+
+	if (hasFlag(Layer::Flag::Renderable))
+		itemFlags << "Renderable";
+
+	if (hasFlag(Layer::Flag::PreventUpdates))
+		itemFlags << "Prevent updates";
+
+	QString toolTip = QString{"%1"}.arg(getName());
+
+	if (!itemFlags.isEmpty())
+		toolTip += "<br><nobr><em>" + itemFlags.join(", ") + "</em></nobr>";
+
+	setToolTip(toolTip);
 }
diff --git a/Grinder/ui/image/LayersListItem.h b/Grinder/ui/image/LayersListItem.h
index 0e6bd3d..3ee1e20 100644
--- a/Grinder/ui/image/LayersListItem.h
+++ b/Grinder/ui/image/LayersListItem.h
@@ -24,6 +24,8 @@ namespace grndr
 		Layer::Type getType() const { return _object->getType(); }
 		bool isVisible() const { return _object->isVisible(); }
 		bool isRemovable() const { return _object->isRemovable(); }
+		Layer::Flags getFlags() const { return _object->getFlags(); }
+		bool hasFlag(Layer::Flag flag) const { return _object->hasFlag(flag); }
 
 	private:
 		QFont _specialFont;
diff --git a/Grinder/ui/image/LayersListItemDelegate.cpp b/Grinder/ui/image/LayersListItemDelegate.cpp
new file mode 100644
index 0000000..d8d59ab
--- /dev/null
+++ b/Grinder/ui/image/LayersListItemDelegate.cpp
@@ -0,0 +1,58 @@
+/******************************************************************************
+ * File: LayersListItemDelegate.cpp
+ * Date: 04.7.2018
+ *****************************************************************************/
+
+#include "Grinder.h"
+#include "LayersListItemDelegate.h"
+#include "LayersListWidget.h"
+#include "res/Resources.h"
+
+#define FLAG_ICON_MARGIN	6
+#define FLAG_ICON_SIZE	QSize{16, 16}
+
+LayersListItemDelegate::LayersListItemDelegate(LayersListWidget* widget, QObject* parent) : QStyledItemDelegate(parent),
+	_widget{widget}, _lockedIcon{FILE_ICON_LOCKED}, _renderableIcon{FILE_ICON_RENDERABLE}, _preventUpdatesIcon{FILE_ICON_PREVENTUPDATES}
+{
+	if (!widget)
+		throw std::invalid_argument{_EXCPT("widget may not be null")};
+}
+
+QSize LayersListItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
+{
+	auto size = QStyledItemDelegate::sizeHint(option, index);
+	size.setHeight(std::max(size.height() + 1, FLAG_ICON_SIZE.height() + 2));
+	return size;
+}
+
+void LayersListItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
+{
+	QStyledItemDelegate::paint(painter, option, index);
+
+	auto lockedFlagRect = QRect{QPoint{0, 0}, FLAG_ICON_SIZE};
+	auto renderableFlagRect = QRect{QPoint{0, 0}, FLAG_ICON_SIZE};
+	auto preventUpdatesFlagRect = QRect{QPoint{0, 0}, FLAG_ICON_SIZE};
+	lockedFlagRect.moveRight(option.rect.right() - (FLAG_ICON_MARGIN / 2));
+	renderableFlagRect.moveRight(lockedFlagRect.left() - FLAG_ICON_MARGIN);
+	preventUpdatesFlagRect.moveRight(renderableFlagRect.left() - FLAG_ICON_MARGIN);
+
+	if (auto layersListItem = _widget->layersListItem(index))
+	{
+		painter->save();
+		drawFlagIcon(painter, _lockedIcon, lockedFlagRect, option.rect, layersListItem->hasFlag(Layer::Flag::Locked));
+		drawFlagIcon(painter, _renderableIcon, renderableFlagRect, option.rect, layersListItem->hasFlag(Layer::Flag::Renderable));
+		drawFlagIcon(painter, _preventUpdatesIcon, preventUpdatesFlagRect, option.rect, layersListItem->hasFlag(Layer::Flag::PreventUpdates));
+		painter->restore();
+	}
+}
+
+void LayersListItemDelegate::drawFlagIcon(QPainter* painter, const QPixmap& icon, QRect iconRect, QRect itemRect, bool flagEnabled) const
+{
+	// Adjust the icon rect to be centered vertically
+	QRect rc = iconRect;
+	rc.moveCenter(itemRect.center());
+	iconRect.moveTop(rc.top() - 1);
+
+	painter->setOpacity(flagEnabled ? 1.0 : 0.2);
+	painter->drawPixmap(iconRect, icon);
+}
diff --git a/Grinder/ui/image/LayersListItemDelegate.h b/Grinder/ui/image/LayersListItemDelegate.h
new file mode 100644
index 0000000..6f9892c
--- /dev/null
+++ b/Grinder/ui/image/LayersListItemDelegate.h
@@ -0,0 +1,39 @@
+/******************************************************************************
+ * File: LayersListItemDelegate.h
+ * Date: 04.7.2018
+ *****************************************************************************/
+
+#ifndef LAYERSLISTITEMDELEGATE_H
+#define LAYERSLISTITEMDELEGATE_H
+
+#include <QStyledItemDelegate>
+
+namespace grndr
+{
+	class LayersListWidget;
+
+	class LayersListItemDelegate : public QStyledItemDelegate
+	{
+		Q_OBJECT
+
+	public:
+		LayersListItemDelegate(LayersListWidget* widget, QObject* parent = nullptr);
+
+	public:
+		virtual QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override;
+
+		virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
+
+	private:
+		void drawFlagIcon(QPainter* painter, const QPixmap& icon, QRect iconRect, QRect itemRect, bool flagEnabled) const;
+
+	private:
+		LayersListWidget* _widget{nullptr};
+
+		QPixmap _lockedIcon;
+		QPixmap _renderableIcon;
+		QPixmap _preventUpdatesIcon;
+	};
+}
+
+#endif
diff --git a/Grinder/ui/image/LayersListWidget.cpp b/Grinder/ui/image/LayersListWidget.cpp
index 45dfcbd..c4ece29 100644
--- a/Grinder/ui/image/LayersListWidget.cpp
+++ b/Grinder/ui/image/LayersListWidget.cpp
@@ -5,6 +5,7 @@
 
 #include "Grinder.h"
 #include "LayersListWidget.h"
+#include "LayersListItemDelegate.h"
 #include "ImageEditor.h"
 #include "image/ImageBuild.h"
 #include "core/GrinderApplication.h"
@@ -17,6 +18,9 @@
 LayersListWidget::LayersListWidget(QWidget* parent) : MetaWidget(parent),
 	_sliderValueWidget{new SliderValueWidget{"Opacity:", 0, 100, "%", nullptr, false}}
 {
+	// Set the delegate for the list items which handles drawing item flags etc.
+	setItemDelegate(new LayersListItemDelegate{this});
+
 	// Create layers actions
 	_renameLayerAction = UIUtils::createAction(this, "Rename &layer", FILE_ICON_EDIT, SLOT(renameLayer()), "Rename the selected layer", "F2");
 	_moveUpAction = UIUtils::createAction(this, "Move &up", FILE_ICON_MOVEUP, SLOT(moveLayerUp()), "Move the selected layer up", "Ctrl+Shift+Up");
@@ -30,6 +34,12 @@ LayersListWidget::LayersListWidget(QWidget* parent) : MetaWidget(parent),
 	_cutLayer->setShortcut(QKeySequence{QKeySequence::Cut});
 	_removeLayerAction = UIUtils::createAction(this, "&Remove layer", FILE_ICON_DELETE, SLOT(removeLayer()), "Remove the selected layer", "Del");
 	_removeAllLayersAction = UIUtils::createAction(this, "Remove all layers", "", SLOT(removeAllLayers()), "Remove all layers");
+	_lockedAction = UIUtils::createAction(this, "Locked", "", SLOT(setLayerLocked()), "Lock the selected layer to prevent changes");
+	_lockedAction->setCheckable(true);
+	_renderableAction = UIUtils::createAction(this, "Renderable", "", SLOT(setLayerRenderable()), "When rendering the output image, include the selected layer");
+	_renderableAction->setCheckable(true);
+	_preventUpdatesAction = UIUtils::createAction(this, "Prevent updates", "", SLOT(setLayerPreventUpdates()), "Prevent changes to the selected layer when executing the pipeline");
+	_preventUpdatesAction->setCheckable(true);
 
 	// Create the alpha slider action
 	auto sliderAction = new QWidgetAction{this};
@@ -117,6 +127,10 @@ std::vector<QAction*> LayersListWidget::getActions(MetaWidget::AddActionsMode mo
 	{
 		actions.push_back(_alphaAction);
 		actions.push_back(nullptr);
+		actions.push_back(_lockedAction);
+		actions.push_back(_renderableAction);
+		actions.push_back(_preventUpdatesAction);
+		actions.push_back(nullptr);
 	}
 
 	actions.push_back(_convertPixelsToItemsAction);
@@ -147,6 +161,15 @@ void LayersListWidget::switchToObjectItem(LayersListItem* item, bool selectItem)
 		_imageEditor->controller().switchLayer(nullptr);
 }
 
+void LayersListWidget::setLayerFlag(Layer::Flag flag, QAction* action)
+{
+	if (auto layerSelected = currentObject())
+	{
+		layerSelected->setFlag(flag, action->isChecked());
+		updateObject(layerSelected);
+	}
+}
+
 void LayersListWidget::newLayer()
 {
 	QString newLayerName = StringUtils::generateUniqueItemName(_imageEditor->controller().activeImageBuild()->layers(), "New layer", &Layer::getName);
@@ -224,6 +247,21 @@ void LayersListWidget::removeAllLayers()
 	updateActions();
 }
 
+void LayersListWidget::setLayerLocked()
+{
+	setLayerFlag(Layer::Flag::Locked, _lockedAction);
+}
+
+void LayersListWidget::setLayerRenderable()
+{
+	setLayerFlag(Layer::Flag::Renderable, _renderableAction);
+}
+
+void LayersListWidget::setLayerPreventUpdates()
+{
+	setLayerFlag(Layer::Flag::PreventUpdates, _preventUpdatesAction);
+}
+
 void LayersListWidget::imageBuildSwitched(ImageBuild* imageBuild)
 {
 	// A new image build is shown in the editor, so populate its layers
@@ -302,7 +340,19 @@ void LayersListWidget::updateActions()
 	_pasteLayer->setEnabled(grinder()->clipboardManager().hasData(LayerVector::Serialization_Element));
 	_cutLayer->setEnabled(layerSelected && currentLayer->isRemovable());
 	_removeLayerAction->setEnabled(layerSelected && currentLayer->isRemovable());
+	_alphaAction->setEnabled(layerSelected);
+	_lockedAction->setEnabled(layerSelected);
+	_renderableAction->setEnabled(layerSelected);
+	_preventUpdatesAction->setEnabled(layerSelected);
 	_removeAllLayersAction->setEnabled(removableLayersCount > 0);
 
 	_sliderValueWidget->setValue(currentLayer ? currentLayer->getAlpha() : 0);
+	_lockedAction->setChecked(currentLayer ? currentLayer->hasFlag(Layer::Flag::Locked) : false);
+	_renderableAction->setChecked(currentLayer ? currentLayer->hasFlag(Layer::Flag::Renderable) : false);
+	_preventUpdatesAction->setChecked(currentLayer ? currentLayer->hasFlag(Layer::Flag::PreventUpdates) : false);
+}
+
+LayersListItem* LayersListWidget::layersListItem(const QModelIndex& index) const
+{
+	return dynamic_cast<LayersListItem*>(itemFromIndex(index));
 }
diff --git a/Grinder/ui/image/LayersListWidget.h b/Grinder/ui/image/LayersListWidget.h
index 3cae37b..0d72792 100644
--- a/Grinder/ui/image/LayersListWidget.h
+++ b/Grinder/ui/image/LayersListWidget.h
@@ -22,6 +22,8 @@ namespace grndr
 	{
 		Q_OBJECT
 
+		friend class LayersListItemDelegate;
+
 	public:
 		LayersListWidget(QWidget* parent = nullptr);
 
@@ -38,6 +40,9 @@ namespace grndr
 	protected:
 		virtual void switchToObjectItem(LayersListItem* item, bool selectItem = true) override;
 
+	private:
+		void setLayerFlag(Layer::Flag flag, QAction* action);
+
 	private slots:
 		void switchLayer() { switchToObjectItem(currentObjectItem()); }
 		void renameLayer() { editItem(currentItem()); }
@@ -50,6 +55,9 @@ namespace grndr
 		void cutLayer();
 		void removeLayer();
 		void removeAllLayers();
+		void setLayerLocked();
+		void setLayerRenderable();
+		void setLayerPreventUpdates();
 
 		void imageBuildSwitched(ImageBuild* imageBuild);
 
@@ -62,6 +70,9 @@ namespace grndr
 		void selectedItemChanged();
 		void updateActions();
 
+	private:
+		LayersListItem* layersListItem(const QModelIndex& index) const;
+
 	private:
 		SliderValueWidget* _sliderValueWidget{nullptr};
 
@@ -76,6 +87,9 @@ namespace grndr
 		QAction* _removeLayerAction{nullptr};
 		QAction* _removeAllLayersAction{nullptr};
 		QAction* _alphaAction{nullptr};
+		QAction* _lockedAction{nullptr};
+		QAction* _renderableAction{nullptr};
+		QAction* _preventUpdatesAction{nullptr};
 	};
 }
 
diff --git a/Grinder/ui/properties/PropertyTreeItemDelegate.cpp b/Grinder/ui/properties/PropertyTreeItemDelegate.cpp
index ec9d579..50e9b9a 100644
--- a/Grinder/ui/properties/PropertyTreeItemDelegate.cpp
+++ b/Grinder/ui/properties/PropertyTreeItemDelegate.cpp
@@ -7,7 +7,6 @@
 #include "PropertyTreeItemDelegate.h"
 #include "PropertyTreeWidget.h"
 #include "ValuePropertyTreeItem.h"
-
 #include "PropertyEditor.h"
 
 PropertyTreeItemDelegate::PropertyTreeItemDelegate(PropertyTreeWidget* widget, QObject* parent) : QStyledItemDelegate(parent),
diff --git a/Grinder/ui/properties/ValuePropertyTreeItem.cpp b/Grinder/ui/properties/ValuePropertyTreeItem.cpp
index ae1a884..4cd834e 100644
--- a/Grinder/ui/properties/ValuePropertyTreeItem.cpp
+++ b/Grinder/ui/properties/ValuePropertyTreeItem.cpp
@@ -40,15 +40,15 @@ void ValuePropertyTreeItem::updateItem()
 		setText(1, property->toString());
 		setToolTip(1, text(1));
 
-		if (property->hasFlag(PropertyBase::Flag::Disabled))
+		if (property->isEnabled())
 		{
-			setFlags(flags()&~Qt::ItemIsEnabled);
-			setTextColor(1, QPalette{}.color(QPalette::Disabled, QPalette::WindowText));
+			setFlags(flags()|Qt::ItemIsEnabled);
+			setTextColor(1, QColor{20, 105, 140});
 		}
 		else
 		{
-			setFlags(flags()|Qt::ItemIsEnabled);
-			setTextColor(1, QColor{20, 105, 140});
+			setFlags(flags()&~Qt::ItemIsEnabled);
+			setTextColor(1, QPalette{}.color(QPalette::Disabled, QPalette::WindowText));
 		}
 	}
 }
diff --git a/Grinder/ui/widgets/ActiveObjectListItem.h b/Grinder/ui/widgets/ActiveObjectListItem.h
index c5e75ef..510b12e 100644
--- a/Grinder/ui/widgets/ActiveObjectListItem.h
+++ b/Grinder/ui/widgets/ActiveObjectListItem.h
@@ -17,7 +17,7 @@ namespace grndr
 		ActiveObjectListItem(ObjectType* object);
 
 	public:
-		virtual void updateItem();
+		virtual void updateItem() override;
 
 	public:
 		bool isActive() const { return _isActive; }
diff --git a/Grinder/ui/widgets/ObjectListWidget.h b/Grinder/ui/widgets/ObjectListWidget.h
index 0acdfef..9c3d351 100644
--- a/Grinder/ui/widgets/ObjectListWidget.h
+++ b/Grinder/ui/widgets/ObjectListWidget.h
@@ -32,8 +32,8 @@ namespace grndr
 
 		object_type* currentObject() const;		
 
-		void updateObject(const object_type* obj) const;
-		void updateAllObjects() const;
+		void updateObject(const object_type* obj);
+		void updateAllObjects();
 
 		template<typename ContainerType>
 		void populateList(const ContainerType& container, bool selectFirst = true, bool reverseOrder = false);
diff --git a/Grinder/ui/widgets/ObjectListWidget.impl.h b/Grinder/ui/widgets/ObjectListWidget.impl.h
index 985cbc4..a5e300e 100644
--- a/Grinder/ui/widgets/ObjectListWidget.impl.h
+++ b/Grinder/ui/widgets/ObjectListWidget.impl.h
@@ -49,20 +49,25 @@ ObjectType* ObjectListWidget<ObjectType, ItemType>::currentObject() const
 }
 
 template<typename ObjectType, typename ItemType>
-void ObjectListWidget<ObjectType, ItemType>::updateObject(const object_type* obj) const
+void ObjectListWidget<ObjectType, ItemType>::updateObject(const object_type* obj)
 {
 	if (auto item = findObjectItem(obj))
+	{
 		item->updateItem();
+		viewport()->update();
+	}
 }
 
 template<typename ObjectType, typename ItemType>
-void ObjectListWidget<ObjectType, ItemType>::updateAllObjects() const
+void ObjectListWidget<ObjectType, ItemType>::updateAllObjects()
 {
 	for (int i = 0; i < count(); ++i)
 	{
 		if (auto item = objectItem(i))
 			item->updateItem();
 	}
+
+	viewport()->update();
 }
 
 template<typename ObjectType, typename ItemType>
-- 
GitLab