From e73584dbafbd59e6780b028ca80e1fb6becf062d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20M=C3=BCller?= <d_muel20@uni-muenster.de>
Date: Fri, 15 Jun 2018 20:16:03 +0200
Subject: [PATCH] * The original input images are now shown as special layers
 in the IE * Layers now have an opacity value

---
 Grinder/Grinder.pro                           | 10 ++-
 Grinder/Version.h                             |  4 +-
 .../serialization/SettingsContainer.cpp       | 16 +++++
 .../common/serialization/SettingsContainer.h  |  9 ++-
 Grinder/controller/ImageEditorController.cpp  | 55 ++++++++++-----
 Grinder/controller/ImageEditorController.h    |  8 ++-
 Grinder/image/ImageBuild.cpp                  | 30 +++++++--
 Grinder/image/ImageBuild.h                    |  2 +-
 Grinder/image/ImageBuildItem.cpp              |  3 +-
 Grinder/image/ImageBuildPool.cpp              |  2 +-
 Grinder/image/Layer.cpp                       | 34 +++++++++-
 Grinder/image/Layer.h                         | 26 ++++++-
 Grinder/image/LayerPixelsData.cpp             |  2 +
 Grinder/image/LayerVector.cpp                 |  5 ++
 Grinder/image/LayerVector.h                   |  1 +
 Grinder/ui/image/DraftItemNode.cpp            | 16 ++---
 Grinder/ui/image/DraftItemNode.h              |  9 ++-
 Grinder/ui/image/ImageEditorScene.cpp         | 61 ++++++++++++-----
 Grinder/ui/image/ImageEditorScene.h           | 36 +++++-----
 Grinder/ui/image/ImageEditorScene.impl.h      | 32 ++-------
 Grinder/ui/image/LayerBackgroundNode.cpp      | 67 +++++++++++++++++++
 Grinder/ui/image/LayerBackgroundNode.h        | 36 ++++++++++
 Grinder/ui/image/LayerItemNode.cpp            | 38 +++++++++++
 Grinder/ui/image/LayerItemNode.h              | 38 +++++++++++
 Grinder/ui/image/LayerPixelsNode.cpp          | 36 +++-------
 Grinder/ui/image/LayerPixelsNode.h            | 19 ++----
 Grinder/ui/image/LayersListItem.cpp           | 12 ++++
 Grinder/ui/image/LayersListItem.h             |  6 ++
 Grinder/ui/image/LayersListWidget.cpp         | 47 +++++++++++--
 Grinder/ui/image/LayersListWidget.h           |  5 ++
 Grinder/ui/widgets/SliderValueWidget.cpp      | 56 ++++++++++++++++
 Grinder/ui/widgets/SliderValueWidget.h        | 41 ++++++++++++
 32 files changed, 599 insertions(+), 163 deletions(-)
 create mode 100644 Grinder/ui/image/LayerBackgroundNode.cpp
 create mode 100644 Grinder/ui/image/LayerBackgroundNode.h
 create mode 100644 Grinder/ui/image/LayerItemNode.cpp
 create mode 100644 Grinder/ui/image/LayerItemNode.h
 create mode 100644 Grinder/ui/widgets/SliderValueWidget.cpp
 create mode 100644 Grinder/ui/widgets/SliderValueWidget.h

diff --git a/Grinder/Grinder.pro b/Grinder/Grinder.pro
index 8c0f367..8dd84fd 100644
--- a/Grinder/Grinder.pro
+++ b/Grinder/Grinder.pro
@@ -249,7 +249,10 @@ SOURCES += \
     pipeline/blocks/MergeChannelsBlock.cpp \
     pipeline/blocks/SplitChannelsBlock.cpp \
     engine/processors/SplitChannelsProcessor.cpp \
-    engine/processors/MergeChannelsProcessor.cpp
+    engine/processors/MergeChannelsProcessor.cpp \
+    ui/image/LayerItemNode.cpp \
+    ui/image/LayerBackgroundNode.cpp \
+    ui/widgets/SliderValueWidget.cpp
 
 HEADERS += \
 	ui/mainwnd/GrinderWindow.h \
@@ -547,7 +550,10 @@ HEADERS += \
     pipeline/blocks/MergeChannelsBlock.h \
     pipeline/blocks/SplitChannelsBlock.h \
     engine/processors/SplitChannelsProcessor.h \
-    engine/processors/MergeChannelsProcessor.h
+    engine/processors/MergeChannelsProcessor.h \
+    ui/image/LayerItemNode.h \
+    ui/image/LayerBackgroundNode.h \
+    ui/widgets/SliderValueWidget.h
 
 FORMS += \
 	ui/mainwnd/GrinderWindow.ui \
diff --git a/Grinder/Version.h b/Grinder/Version.h
index 0ddd137..1423d8e 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			"14.06.2018"
+#define GRNDR_INFO_DATE			"15.06.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		5
 #define GRNDR_VERSION_REVISION	0
-#define GRNDR_VERSION_BUILD		185
+#define GRNDR_VERSION_BUILD		189
 
 namespace grndr
 {
diff --git a/Grinder/common/serialization/SettingsContainer.cpp b/Grinder/common/serialization/SettingsContainer.cpp
index e58a0a3..8fa898d 100644
--- a/Grinder/common/serialization/SettingsContainer.cpp
+++ b/Grinder/common/serialization/SettingsContainer.cpp
@@ -67,3 +67,19 @@ void SettingsContainer::removeChildren(QString name)
 
 	std::remove_if(_childContainers.begin(), _childContainers.end(), [name](const auto& child) { return child->_name.compare(name, Qt::CaseInsensitive) == 0; });
 }
+
+QVariant& SettingsContainer::value(QString name, QVariant defaultValue)
+{
+	if (!contains(name))
+		_values[name] = defaultValue;
+
+	return _values[name];
+}
+
+QVariant SettingsContainer::value(QString name, QVariant defaultValue) const
+{
+	if (contains(name))
+		return _values[name];
+	else
+		return defaultValue;
+}
diff --git a/Grinder/common/serialization/SettingsContainer.h b/Grinder/common/serialization/SettingsContainer.h
index 9578bab..d976df5 100644
--- a/Grinder/common/serialization/SettingsContainer.h
+++ b/Grinder/common/serialization/SettingsContainer.h
@@ -50,13 +50,18 @@ namespace grndr
 		void clearChildren() { _childContainers.clear(); }
 
 	public:
+		QVariant& value(QString name, QVariant defaultValue = QVariant{});
+		QVariant value(QString name, QVariant defaultValue = QVariant{}) const;
+
 		QVariant& operator[](QString name) { return _values[name]; }
-		const QVariant operator[](QString name) const { return _values[name]; }
+		QVariant operator[](QString name) const { return _values[name]; }
+		QVariant& operator()(QString name, QVariant defaultValue = QVariant{}) { return value(name, defaultValue); }
+		QVariant operator()(QString name, QVariant defaultValue = QVariant{}) const { return value(name, defaultValue); }
 
 		const QVariantMap& values() const { return _values; }
 
 		QStringList keys() const { return _values.keys(); }
-		bool contains(QString name) { return _values.find(name) != _values.end(); }
+		bool contains(QString name) const { return _values.find(name) != _values.cend(); }
 
 		void removeValue(QString name) { _values.remove(name); }
 		void clearValues() { _values.clear(); }
diff --git a/Grinder/controller/ImageEditorController.cpp b/Grinder/controller/ImageEditorController.cpp
index b88b393..4c86f17 100644
--- a/Grinder/controller/ImageEditorController.cpp
+++ b/Grinder/controller/ImageEditorController.cpp
@@ -208,7 +208,7 @@ void ImageEditorController::pasteLayer()
 		auto layers = grinder()->clipboardManager().deserialize<Layer>(LayerVector::Serialization_Element, [this](const SettingsContainer& settings) {
 			QString name = settings[Layer::Serialization_Value_Name].toString();
 
-			if (_activeImageBuild->layers().selectByName(name))
+			while (_activeImageBuild->layers().selectByName(name))
 				name += " - Copy";
 
 			return createLayer(name).get();
@@ -228,9 +228,9 @@ void ImageEditorController::cutLayer(const Layer* layer)
 	removeLayer(layer);
 }
 
-void ImageEditorController::removeLayer(const Layer* layer)
+void ImageEditorController::removeLayer(const Layer* layer, bool enforceRemoval)
 {
-	if (layer)
+	if (layer && (layer->isRemovable() || enforceRemoval))
 	{
 		// If removing the active layer, unset it first
 		if (_activeLayer == layer)
@@ -243,10 +243,27 @@ void ImageEditorController::removeLayer(const Layer* layer)
 	}
 }
 
-void ImageEditorController::removeAllLayers()
+void ImageEditorController::removeAllLayers(bool enforceRemoval)
 {
-	if (_activeImageBuild)
-		_activeImageBuild->removeAllLayers();
+	if (_activeImageBuild)		
+	{
+		if (!enforceRemoval)
+		{
+			// Only remove all removable layers
+			std::vector<Layer*> layersToRemove;
+
+			for (auto layer : _activeImageBuild->layers())
+			{
+				if (layer->isRemovable())
+					layersToRemove.push_back(layer.get());
+			}
+
+			for (auto layer : layersToRemove)
+				removeLayer(layer, true);
+		}
+		else
+			_activeImageBuild->removeAllLayers();
+	}
 }
 
 std::shared_ptr<DraftItem> ImageEditorController::createDraftItem(DraftItemType type, Layer* layer, bool autoCreateLayer)
@@ -286,6 +303,11 @@ void ImageEditorController::setLayerVisibility(Layer* layer, bool visible) const
 	layer->setVisible(visible);
 }
 
+void ImageEditorController::setLayerAlpha(Layer* layer, float alpha) const
+{
+	layer->setAlpha(alpha);
+}
+
 void ImageEditorController::moveLayer(Layer* layer, bool up) const
 {
 	callControllerFunction("Moving a layer", [this](Layer* layer, bool up) {
@@ -612,6 +634,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);
 	}
 	else
 		disconnect(layer, nullptr, this, nullptr);
@@ -662,10 +685,7 @@ void ImageEditorController::layerMoved(const std::shared_ptr<Layer>& layer, int
 
 	// Reflect the new z-order in the scene
 	if (_activeScene)
-	{
-		_activeScene->updateDraftItemsOrder();
-		_activeScene->updateLayerPixelsOrder();
-	}
+		_activeScene->updateLayerItemsOrder();
 
 	if (_layersList)
 		_layersList->swapLayers(indexOld, indexNew);
@@ -675,11 +695,16 @@ void ImageEditorController::layerVisibilityChanged() const
 {
 	// Update the visibility of all items in the layer
 	if (_activeScene)
-	{
-		auto layer = dynamic_cast<Layer*>(sender());
-		_activeScene->updateDraftItemsVisibility(layer);
-		_activeScene->updateLayerPixelsVisibility(layer);
-	}
+		_activeScene->updateLayerItemsVisibility(dynamic_cast<Layer*>(sender()));
+}
+
+void ImageEditorController::layerAlphaChanged(float alpha) const
+{
+	Q_UNUSED(alpha);
+
+	// Update the alpha value of all items in the layer
+	if (_activeScene)
+		_activeScene->updateLayerItemsAlpha(dynamic_cast<Layer*>(sender()));
 }
 
 void ImageEditorController::draftItemCreated(const std::shared_ptr<DraftItem>& item) const
diff --git a/Grinder/controller/ImageEditorController.h b/Grinder/controller/ImageEditorController.h
index 992d597..e215aef 100644
--- a/Grinder/controller/ImageEditorController.h
+++ b/Grinder/controller/ImageEditorController.h
@@ -51,13 +51,14 @@ namespace grndr
 		void copyLayer(const Layer* layer) const;
 		void pasteLayer();
 		void cutLayer(const Layer* layer);
-		void removeLayer(const Layer* layer);
-		void removeAllLayers();
+		void removeLayer(const Layer* layer, bool enforceRemoval = false);
+		void removeAllLayers(bool enforceRemoval = false);
 
 		std::shared_ptr<DraftItem> createDraftItem(DraftItemType type, Layer* layer = nullptr, bool autoCreateLayer = true);
 		void removeDraftItem(const DraftItem* item) const;
 
-		void setLayerVisibility(Layer* layer, bool visible) const;		
+		void setLayerVisibility(Layer* layer, bool visible) const;
+		void setLayerAlpha(Layer* layer, float alpha) const;
 		void moveLayer(Layer* layer, bool up) const;
 		void renameLayer(Layer* layer, QString newName) const;
 		void renameImageBuildItem(ImageBuildItem* item, QString newName) const;
@@ -103,6 +104,7 @@ namespace grndr
 		void layerRemoved(const std::shared_ptr<Layer>& layer) const;
 		void layerMoved(const std::shared_ptr<Layer>& layer, int indexOld, int indexNew) const;
 		void layerVisibilityChanged() const;
+		void layerAlphaChanged(float alpha) const;
 
 		void draftItemCreated(const std::shared_ptr<DraftItem>& item) const;
 		void draftItemRemoved(const std::shared_ptr<DraftItem>& item) const;
diff --git a/Grinder/image/ImageBuild.cpp b/Grinder/image/ImageBuild.cpp
index ff31fc4..27c817d 100644
--- a/Grinder/image/ImageBuild.cpp
+++ b/Grinder/image/ImageBuild.cpp
@@ -7,6 +7,7 @@
 #include "ImageBuild.h"
 #include "ImageExceptions.h"
 #include "pipeline/Block.h"
+#include "project/ImageReference.h"
 #include "properties/ImageTagsProperty.h"
 
 const char* ImageBuild::Serialization_Value_ImageReferences = "ImageReferences";
@@ -31,12 +32,19 @@ ImageBuild::ImageBuild(const Block* block, const std::vector<const ImageReferenc
 void ImageBuild::initImageBuild()
 {
 	createProperties();
+
+	// Create background image layers for all assigned image references
+	for (const auto imageRef : _imageReferences)
+	{
+		if (auto backgroundLayer = createLayer(QString{"Bg: %1"}.arg(imageRef->getImageFileName()), Layer::Type::BackgroundImage, imageRef))
+			backgroundLayer->setVisible(false);
+	}
 }
 
-std::shared_ptr<Layer> ImageBuild::createLayer(QString name)
+std::shared_ptr<Layer> ImageBuild::createLayer(QString name, Layer::Type layerType, const ImageReference* backgroundImage)
 {
 	// Create new layer
-	auto layer = std::make_shared<Layer>(this, name);
+	auto layer = std::make_shared<Layer>(this, name, layerType, backgroundImage);
 
 	try {	// Propagate initialization errors to the caller
 		layer->initLayer();
@@ -138,9 +146,23 @@ void ImageBuild::deserialize(DeserializationContext& ctx)
 	// Deserialize all layers
 	if (ctx.beginGroup(LayerVector::Serialization_Group))
 	{
-		_layers.deserialize(LayerVector::Serialization_Element, ctx, [this](const SettingsContainer& settings) {
+		_layers.deserialize(LayerVector::Serialization_Element, ctx, [this, ctx](const SettingsContainer& settings) {
+			Layer::Type type = static_cast<Layer::Type>(settings[Layer::Serialization_Value_Type].toInt());
+			int backgroundImageIndex = settings[Layer::Serialization_Value_BackgroundImage].toInt();
+			const ImageReference* backgroundImage = nullptr;
+
+			// Search for an existing background image layer (as they are created during initialization)
+			if (backgroundImageIndex != -1 && type != Layer::Type::Standard)
+			{
+				if ((backgroundImage = ctx.getImageReference(backgroundImageIndex)))
+				{
+					if (auto backgroundLayer = _layers.selectByBackgroundImage(backgroundImage))
+						return backgroundLayer;
+				}
+			}
+
 			QString name = settings[Layer::Serialization_Value_Name].toString();
-			return createLayer(name);
+			return createLayer(name, type, backgroundImage);
 		});
 
 		ctx.endGroup();
diff --git a/Grinder/image/ImageBuild.h b/Grinder/image/ImageBuild.h
index e740f66..b9e8057 100644
--- a/Grinder/image/ImageBuild.h
+++ b/Grinder/image/ImageBuild.h
@@ -31,7 +31,7 @@ namespace grndr
 		void initImageBuild();
 
 	public:
-		std::shared_ptr<Layer> createLayer(QString name = "");
+		std::shared_ptr<Layer> createLayer(QString name = "", Layer::Type layerType = Layer::Type::Standard, const ImageReference* backgroundImage = nullptr);
 		void removeLayer(const Layer* layer);
 		void removeAllLayers();
 
diff --git a/Grinder/image/ImageBuildItem.cpp b/Grinder/image/ImageBuildItem.cpp
index fe785e4..f4a5066 100644
--- a/Grinder/image/ImageBuildItem.cpp
+++ b/Grinder/image/ImageBuildItem.cpp
@@ -28,5 +28,6 @@ void ImageBuildItem::serialize(SerializationContext& ctx) const
 void ImageBuildItem::deserialize(DeserializationContext& ctx)
 {
 	// Deserialize values
-	_name = ctx.settings()[Serialization_Value_Name].toString();
+	if (ctx.getMode() != DeserializationContext::Mode::ClipboardSerialization)
+		_name = ctx.settings()[Serialization_Value_Name].toString();
 }
diff --git a/Grinder/image/ImageBuildPool.cpp b/Grinder/image/ImageBuildPool.cpp
index 527c355..6983c61 100644
--- a/Grinder/image/ImageBuildPool.cpp
+++ b/Grinder/image/ImageBuildPool.cpp
@@ -67,7 +67,7 @@ std::shared_ptr<ImageBuild> ImageBuildPool::imageBuild(const Block* block, const
 std::shared_ptr<ImageBuild> ImageBuildPool::imageBuild(const Block* block, const ImageReference* activeImageReference)
 {
 	// Get the image build for the given block and all its used image references
-	std::set<const ImageReference*> imageReferences = block->assembleInputImageReferences(activeImageReference);
+	auto imageReferences = block->assembleInputImageReferences(activeImageReference);
 	return imageBuild(block, std::vector<const ImageReference*>{imageReferences.cbegin(), imageReferences.cend()});
 }
 
diff --git a/Grinder/image/Layer.cpp b/Grinder/image/Layer.cpp
index 9a13a09..6b3310d 100644
--- a/Grinder/image/Layer.cpp
+++ b/Grinder/image/Layer.cpp
@@ -9,12 +9,16 @@
 #include "ImageExceptions.h"
 #include "DraftItemCatalog.h"
 
+const char* Layer::Serialization_Value_Type = "Type";
 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) : ImageBuildItem(imageBuild, name),
-	_draftItems{this}, _layerPixels{this}
+Layer::Layer(ImageBuild* imageBuild, QString name, Type type, const ImageReference* backgroundImage) : ImageBuildItem(imageBuild, name),
+	_type{type}, _draftItems{this}, _layerPixels{this}, _backgroundImage{backgroundImage}
 {
-
+	if (type == Type::Standard)	// Standard layers don't have a background
+		_backgroundImage = nullptr;
 }
 
 void Layer::initLayer()
@@ -81,12 +85,27 @@ void Layer::setVisible(bool visible)
 	}
 }
 
+void Layer::setAlpha(unsigned int alpha)
+{
+	if (alpha > 100)
+		alpha = 100;
+
+	if (alpha != _alpha)
+	{
+		_alpha = alpha;
+		emit layerAlphaChanged(_alpha);
+	}
+}
+
 void Layer::serialize(SerializationContext& ctx) const
 {
 	ImageBuildItem::serialize(ctx);
 
 	// Serialize values
+	ctx.settings()[Serialization_Value_Type] = static_cast<int>(_type);
 	ctx.settings()[Serialization_Value_Visible] = _isVisible;
+	ctx.settings()[Serialization_Value_Alpha] = _alpha;
+	ctx.settings()[Serialization_Value_BackgroundImage] = _backgroundImage ? ctx.getImageReferenceIndex(_backgroundImage) : -1;
 
 	// Serialize all draft items
 	ctx.beginGroup(DraftItemVector::Serialization_Group, true);
@@ -104,8 +123,17 @@ void Layer::deserialize(DeserializationContext& ctx)
 	ImageBuildItem::deserialize(ctx);
 
 	// Deserialize values
+	_type = static_cast<Type>(ctx.settings()[Serialization_Value_Type].toInt());
+	_alpha = ctx.settings()(Serialization_Value_Alpha, 100).toUInt();
 	_isVisible = ctx.settings()[Serialization_Value_Visible].toBool();
 
+	int backgroundImageIndex = ctx.settings()(Serialization_Value_BackgroundImage, -1).toInt();
+
+	if (backgroundImageIndex != -1)
+		_backgroundImage = ctx.getImageReference(backgroundImageIndex);
+	else
+		_backgroundImage = nullptr;
+
 	// Deserialize all draft items
 	if (ctx.beginGroup(DraftItemVector::Serialization_Group))
 	{
diff --git a/Grinder/image/Layer.h b/Grinder/image/Layer.h
index 0354293..d4c8b16 100644
--- a/Grinder/image/Layer.h
+++ b/Grinder/image/Layer.h
@@ -17,10 +17,19 @@ namespace grndr
 		Q_OBJECT
 
 	public:
+		static const char* Serialization_Value_Type;
 		static const char* Serialization_Value_Visible;
+		static const char* Serialization_Value_Alpha;
+		static const char* Serialization_Value_BackgroundImage;
+
+		enum class Type
+		{
+			Standard,
+			BackgroundImage,
+		};
 
 	public:
-		Layer(ImageBuild* imageBuild, QString name = "");
+		Layer(ImageBuild* imageBuild, QString name = "", Type type = Type::Standard, const ImageReference* backgroundImage = nullptr);
 
 	public:
 		void initLayer();
@@ -30,15 +39,24 @@ namespace grndr
 		void removeDraftItem(const DraftItem* item);
 
 	public:
+		Type getType() const { return _type; }
+
 		int getZOrder() const;
 
 		bool isVisible() const { return _isVisible; }
 		void setVisible(bool visible = true);
+		unsigned int getAlpha() const { return _alpha; }
+		void setAlpha(unsigned int alpha);
+
+		bool isRemovable() const { return _type == Type::Standard; }
 
 		const DraftItemVector& draftItems() const { return _draftItems; }
 		LayerPixels& layerPixels() { return _layerPixels; }
 		const LayerPixels& layerPixels() const { return _layerPixels; }
 
+		const ImageReference* backgroundImage() const { return _backgroundImage; }
+		void setBackgroundImage(const ImageReference* imageRef) { _backgroundImage = imageRef; }
+
 	public:
 		virtual void serialize(SerializationContext& ctx) const override;
 		virtual void deserialize(DeserializationContext& ctx) override;
@@ -46,15 +64,21 @@ namespace grndr
 	signals:
 		void layerShown();
 		void layerHidden();
+		void layerAlphaChanged(unsigned int);
 
 		void draftItemCreated(const std::shared_ptr<DraftItem>&);
 		void draftItemRemoved(const std::shared_ptr<DraftItem>&);
 
 	private:
+		Type _type{Type::Standard};
+
 		bool _isVisible{true};
+		unsigned int _alpha{100};
 
 		DraftItemVector _draftItems;
 		LayerPixels _layerPixels;
+
+		const ImageReference* _backgroundImage{nullptr};
 	};
 }
 
diff --git a/Grinder/image/LayerPixelsData.cpp b/Grinder/image/LayerPixelsData.cpp
index 4c79d31..e358ae6 100644
--- a/Grinder/image/LayerPixelsData.cpp
+++ b/Grinder/image/LayerPixelsData.cpp
@@ -110,4 +110,6 @@ void LayerPixelsData::deserialize(DeserializationContext& ctx)
 	// Deserialize all color map entries
 	for (auto it = ctx.settings().values().begin(); it != ctx.settings().values().end(); ++it)
 		_colorMap[QColor{it.key()}].fromString(it->toString());
+
+	emit dataModified();
 }
diff --git a/Grinder/image/LayerVector.cpp b/Grinder/image/LayerVector.cpp
index 9a989af..395fb05 100644
--- a/Grinder/image/LayerVector.cpp
+++ b/Grinder/image/LayerVector.cpp
@@ -20,3 +20,8 @@ LayerVector::pointer_type LayerVector::selectByName(QString name, bool caseSensi
 {
 	return selectFirst([name, caseSensitive](auto layer) { return layer->getName().compare(name, caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive) == 0; });
 }
+
+LayerVector::pointer_type LayerVector::selectByBackgroundImage(const ImageReference* imageRef) const
+{
+	return selectFirst([imageRef](auto layer) { return layer->backgroundImage() == imageRef; });
+}
diff --git a/Grinder/image/LayerVector.h b/Grinder/image/LayerVector.h
index 678bb0d..65d9554 100644
--- a/Grinder/image/LayerVector.h
+++ b/Grinder/image/LayerVector.h
@@ -22,6 +22,7 @@ namespace grndr
 
 	public:
 		pointer_type selectByName(QString name, bool caseSensitive = false) const;
+		pointer_type selectByBackgroundImage(const ImageReference* imageRef) const;
 
 	private:
 		const ImageBuild* _imageBuild{nullptr};
diff --git a/Grinder/ui/image/DraftItemNode.cpp b/Grinder/ui/image/DraftItemNode.cpp
index 924c090..b6c75b1 100644
--- a/Grinder/ui/image/DraftItemNode.cpp
+++ b/Grinder/ui/image/DraftItemNode.cpp
@@ -13,7 +13,7 @@
 #include "util/MathUtils.h"
 #include "res/Resources.h"
 
-DraftItemNode::DraftItemNode(ImageEditorScene* scene, const std::shared_ptr<DraftItem>& item, QGraphicsItem* parent) : ImageEditorNode(scene, parent),
+DraftItemNode::DraftItemNode(ImageEditorScene* scene, const std::shared_ptr<DraftItem>& item, QGraphicsItem* parent) : LayerItemNode(scene, item->layer(), parent),
 	_draftItem{item}, _draftItemStyle{_style.getItemNodeStyle()}
 {
 	if (!item)
@@ -37,6 +37,7 @@ DraftItemNode::DraftItemNode(ImageEditorScene* scene, const std::shared_ptr<Draf
 
 	updateZOrder();
 	updateVisibility();
+	updateAlpha();
 
 	// Create node actions
 	_deleteAction = createNodeAction("&Delete item(s)", FILE_ICON_DELETE, SLOT(deleteNode()), "Remove the selected items", "Del");
@@ -67,17 +68,14 @@ void DraftItemNode::updateNode()
 		for (const auto& imageTag : draftItem->imageTagsAllotment()->object().allottedTags())
 			imageTags << imageTag->getName();
 
-		setToolTip(QString{"<b>Tags:</b> %1"}.arg(!imageTags.isEmpty() ? imageTags.join(", ") : "None"));
+		setToolTip(QString{"<b>Layer:</b> %2<br><b>Tags:</b> %1"}.arg(!imageTags.isEmpty() ? imageTags.join(", ") : "None").arg(draftItem->layer()->getName()));
 	}
 
-	prepareGeometryChange();
-	updateGeometry();
+	LayerItemNode::updateNode();
 
 	// Also update the in-place editor
 	if (_inPlaceEditor)
 		_inPlaceEditor->updateEditor();
-
-	update();
 }
 
 void DraftItemNode::updateZOrder()
@@ -93,12 +91,6 @@ void DraftItemNode::updateZOrder()
 	}
 }
 
-void DraftItemNode::updateVisibility()
-{
-	if (auto draftItem = _draftItem.lock())	// Make sure that the underlying draft item still exists
-		setVisible(draftItem->layer()->isVisible());	// Use the visibility flag of the layer
-}
-
 QVariant DraftItemNode::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant& value)
 {
 	if (change == QGraphicsItem::ItemPositionHasChanged)
diff --git a/Grinder/ui/image/DraftItemNode.h b/Grinder/ui/image/DraftItemNode.h
index 13d68d2..14ca5d3 100644
--- a/Grinder/ui/image/DraftItemNode.h
+++ b/Grinder/ui/image/DraftItemNode.h
@@ -6,13 +6,13 @@
 #ifndef DRAFTITEMNODE_H
 #define DRAFTITEMNODE_H
 
-#include "ImageEditorNode.h"
+#include "LayerItemNode.h"
 #include "ImageEditorStyle.h"
 #include "InPlaceEditor.h"
 
 namespace grndr
 {
-	class DraftItemNode : public ImageEditorNode
+	class DraftItemNode : public LayerItemNode
 	{
 		Q_OBJECT
 
@@ -27,10 +27,9 @@ namespace grndr
 		const std::weak_ptr<DraftItem>& draftItem() const { return _draftItem; }
 
 	public slots:
-		void updateNode();
+		virtual void updateNode() override;
 
-		void updateZOrder();
-		void updateVisibility();
+		virtual void updateZOrder() override;
 
 	protected:
 		virtual std::unique_ptr<InPlaceEditor> createInPlaceEditor() { return std::unique_ptr<InPlaceEditor>{}; }
diff --git a/Grinder/ui/image/ImageEditorScene.cpp b/Grinder/ui/image/ImageEditorScene.cpp
index 580c95e..e20434c 100644
--- a/Grinder/ui/image/ImageEditorScene.cpp
+++ b/Grinder/ui/image/ImageEditorScene.cpp
@@ -7,6 +7,7 @@
 #include "ImageEditorScene.h"
 #include "ImageEditor.h"
 #include "DraftItemNode.h"
+#include "LayerBackgroundNode.h"
 #include "LayerPixelsNode.h"
 #include "image/ImageExceptions.h"
 #include "controller/ImageEditorController.h"
@@ -39,6 +40,7 @@ void ImageEditorScene::buildScene()
 			for (const auto& item : layer->draftItems())
 				createDraftItemNode(item);
 
+			createLayerBackgroundNode(layer);
 			createLayerPixelsNode(layer);
 		}
 	}
@@ -100,16 +102,22 @@ void ImageEditorScene::removeDraftItemNode(const std::shared_ptr<DraftItem>& ite
 	removeNode(findDraftItemNode(item.get()));
 }
 
-void ImageEditorScene::updateDraftItemsOrder()
+void ImageEditorScene::createLayerBackgroundNode(const std::shared_ptr<Layer>& layer)
 {
-	for (auto& draftItem : findDraftItemNodes())
-		draftItem->updateZOrder();
+	if (!layer)
+		throw std::invalid_argument{_EXCPT("layer may not be null")};
+
+	if (auto backgroundImage = layer->backgroundImage())
+	{
+		if (!findLayerBackgroundNode(backgroundImage))
+			addItem(new LayerBackgroundNode{this, layer.get()});
+	}
 }
 
-void ImageEditorScene::updateDraftItemsVisibility(const Layer* layer)
+void ImageEditorScene::removeLayerBackgroundNode(const std::shared_ptr<Layer>& layer)
 {
-	for (auto& draftItem : findDraftItemNodes(layer))
-		draftItem->updateVisibility();
+	if (auto backgroundImage = layer->backgroundImage())
+		removeNode(findLayerBackgroundNode(backgroundImage));
 }
 
 void ImageEditorScene::createLayerPixelsNode(const std::shared_ptr<Layer>& layer)
@@ -118,7 +126,7 @@ void ImageEditorScene::createLayerPixelsNode(const std::shared_ptr<Layer>& layer
 		throw std::invalid_argument{_EXCPT("layer may not be null")};
 
 	if (!findLayerPixelsNode(&layer->layerPixels()))
-		addItem(new LayerPixelsNode{this, layer});
+		addItem(new LayerPixelsNode{this, layer.get()});
 }
 
 void ImageEditorScene::removeLayerPixelsNode(const std::shared_ptr<Layer>& layer)
@@ -126,16 +134,22 @@ void ImageEditorScene::removeLayerPixelsNode(const std::shared_ptr<Layer>& layer
 	removeNode(findLayerPixelsNode(&layer->layerPixels()));
 }
 
-void ImageEditorScene::updateLayerPixelsOrder()
+void ImageEditorScene::updateLayerItemsOrder()
 {
-	for (auto& layerPixels : findLayerPixelsNodes())
-		layerPixels->updateZOrder();
+	for (auto& layerItem : findLayerItemNodes())
+		layerItem->updateZOrder();
 }
 
-void ImageEditorScene::updateLayerPixelsVisibility(const Layer* layer)
+void ImageEditorScene::updateLayerItemsVisibility(const Layer* layer)
 {
-	for (auto& layerPixels : findLayerPixelsNodes(layer))
-		layerPixels->updateVisibility();
+	for (auto& layerItem : findLayerItemNodes(layer))
+		layerItem->updateVisibility();
+}
+
+void ImageEditorScene::updateLayerItemsAlpha(const Layer* layer)
+{
+	for (auto& layerItem : findLayerItemNodes(layer))
+		layerItem->updateAlpha();
 }
 
 void ImageEditorScene::mousePressEvent(QGraphicsSceneMouseEvent* mouseEvent)
@@ -219,17 +233,28 @@ DraftItemNode* ImageEditorScene::_findDraftItemNode(const DraftItem* item) const
 	return nullptr;
 }
 
+LayerBackgroundNode* ImageEditorScene::_findLayerBackgroundNode(const ImageReference* imageRef) const
+{
+	for (const auto& node : items())
+	{
+		if (auto itemNode = dynamic_cast<LayerBackgroundNode*>(node))
+		{
+			if (itemNode->layer()->backgroundImage() == imageRef)
+				return itemNode;
+		}
+	}
+
+	return nullptr;
+}
+
 LayerPixelsNode* ImageEditorScene::_findLayerPixelsNode(const LayerPixels* layerPixels) const
 {
 	for (const auto& node : items())
 	{
 		if (auto itemNode = dynamic_cast<LayerPixelsNode*>(node))
 		{
-			if (auto layer = itemNode->layer().lock())
-			{
-				if (&layer->layerPixels() == layerPixels)
-					return itemNode;
-			}
+			if (&itemNode->layer()->layerPixels() == layerPixels)
+				return itemNode;
 		}
 	}
 
diff --git a/Grinder/ui/image/ImageEditorScene.h b/Grinder/ui/image/ImageEditorScene.h
index e6e2a35..bd658ef 100644
--- a/Grinder/ui/image/ImageEditorScene.h
+++ b/Grinder/ui/image/ImageEditorScene.h
@@ -16,7 +16,8 @@
 namespace grndr
 {
 	class ImageEditor;
-	class ImageEditorNode;
+	class LayerItemNode;
+	class LayerBackgroundNode;
 	class LayerPixelsNode;
 
 	class ImageEditorScene : public VisualScene<ImageEditorView>, public ImageEditorComponent
@@ -35,27 +36,30 @@ namespace grndr
 		void createDraftItemNode(const std::shared_ptr<DraftItem>& item);
 		void removeDraftItemNode(const std::shared_ptr<DraftItem>& item);
 
-		void updateDraftItemsOrder();
-		void updateDraftItemsVisibility(const Layer* layer = nullptr);
-
-		void createLayerPixelsNode(const std::shared_ptr<Layer>& layer);
+		void createLayerBackgroundNode(const std::shared_ptr<Layer>& layer);
+		void removeLayerBackgroundNode(const std::shared_ptr<Layer>& layer);
+		void createLayerPixelsNode(const std::shared_ptr<Layer>& layer);		
 		void removeLayerPixelsNode(const std::shared_ptr<Layer>& layer);
 
-		void updateLayerPixelsOrder();
-		void updateLayerPixelsVisibility(const Layer* layer = nullptr);
+		void updateLayerItemsOrder();
+		void updateLayerItemsVisibility(const Layer* layer);
+		void updateLayerItemsAlpha(const Layer* layer);
 
 		void removeNode(QGraphicsItem* node) { if (node) delete node; }
 
 	public:
+		template<typename ValType = LayerItemNode>
+		std::vector<ValType*> findLayerItemNodes(const Layer* layer = nullptr) { return _findLayerItemNodes<ValType>(layer); }
+		template<typename ValType = LayerItemNode>
+		std::vector<const ValType*> findLayerItemNodes(const Layer* layer = nullptr) const { return _findLayerItemNodes<const ValType>(layer); }
+
 		DraftItemNode* findDraftItemNode(const DraftItem* item) { return _findDraftItemNode(item); }
 		const DraftItemNode* findDraftItemNode(const DraftItem* item) const { return _findDraftItemNode(item); }
-		std::vector<DraftItemNode*> findDraftItemNodes(const Layer* layer = nullptr) { return _findDraftItemNodes<DraftItemNode*>(layer); }
-		std::vector<const DraftItemNode*> findDraftItemNodes(const Layer* layer = nullptr) const { return _findDraftItemNodes<const DraftItemNode*>(layer); }
 
-		LayerPixelsNode* findLayerPixelsNode(const LayerPixels* layerPixels) { return _findLayerPixelsNode(layerPixels); }
+		LayerBackgroundNode* findLayerBackgroundNode(const ImageReference* imageRef) { return _findLayerBackgroundNode(imageRef); }
+		const LayerBackgroundNode* findLayerBackgroundNode(const ImageReference* imageRef) const { return _findLayerBackgroundNode(imageRef); }
+		LayerPixelsNode* findLayerPixelsNode(const LayerPixels* layerPixels) { return _findLayerPixelsNode(layerPixels); }		
 		const LayerPixelsNode* findLayerPixelsNode(const LayerPixels* layerPixels) const { return _findLayerPixelsNode(layerPixels); }
-		std::vector<LayerPixelsNode*> findLayerPixelsNodes(const Layer* layer = nullptr) { return _findLayerPixelsNodes<LayerPixelsNode*>(layer); }
-		std::vector<const LayerPixelsNode*> findLayerPixelsNodes(const Layer* layer = nullptr) const { return _findLayerPixelsNodes<const LayerPixelsNode*>(layer); }
 
 	public:
 		ImageBuild* imageBuild() { return _imageBuild; }
@@ -78,13 +82,13 @@ namespace grndr
 		ImageEditorTool::InputEventResult handleKeyEvent(QKeyEvent* keyEvent, std::function<ImageEditorTool::InputEventResult(ImageEditorTool*, QKeyEvent*)> handler);
 
 	private:
-		DraftItemNode* _findDraftItemNode(const DraftItem* item) const;
 		template<typename ValType>
-		std::vector<ValType> _findDraftItemNodes(const Layer* layer = nullptr) const;
+		std::vector<ValType*> _findLayerItemNodes(const Layer* layer = nullptr) const;
 
+		DraftItemNode* _findDraftItemNode(const DraftItem* item) const;
+
+		LayerBackgroundNode* _findLayerBackgroundNode(const ImageReference* imageRef) const;
 		LayerPixelsNode* _findLayerPixelsNode(const LayerPixels* layerPixels) const;
-		template<typename ValType>
-		std::vector<ValType> _findLayerPixelsNodes(const Layer* layer = nullptr) const;
 
 	private:
 		ImageBuild* _imageBuild{nullptr};
diff --git a/Grinder/ui/image/ImageEditorScene.impl.h b/Grinder/ui/image/ImageEditorScene.impl.h
index 6d5fc97..9bc504f 100644
--- a/Grinder/ui/image/ImageEditorScene.impl.h
+++ b/Grinder/ui/image/ImageEditorScene.impl.h
@@ -9,38 +9,16 @@
 #include "LayerPixelsNode.h"
 
 template<typename ValType>
-std::vector<ValType> ImageEditorScene::_findDraftItemNodes(const Layer* layer) const
+std::vector<ValType*> ImageEditorScene::_findLayerItemNodes(const Layer* layer) const
 {
-	std::vector<ValType> nodes;
+	std::vector<ValType*> nodes;
 
 	for (const auto& node : items())
 	{
-		if (auto itemNode = dynamic_cast<DraftItemNode*>(node))
+		if (auto itemNode = dynamic_cast<ValType*>(node))
 		{
-			if (auto item = itemNode->draftItem().lock())
-			{
-				if (!layer || item->layer() == layer)
-					nodes.push_back(itemNode);
-			}
-		}
-	}
-
-	return nodes;
-}
-template<typename ValType>
-std::vector<ValType> ImageEditorScene::_findLayerPixelsNodes(const Layer* layer) const
-{
-	std::vector<ValType> nodes;
-
-	for (const auto& node : items())
-	{
-		if (auto pixelsNode = dynamic_cast<LayerPixelsNode*>(node))
-		{
-			if (auto pixelsLayer = pixelsNode->layer().lock())
-			{
-				if (!layer || pixelsLayer.get() == layer)
-					nodes.push_back(pixelsNode);
-			}
+			if (!layer || itemNode->layer() == layer)
+				nodes.push_back(itemNode);
 		}
 	}
 
diff --git a/Grinder/ui/image/LayerBackgroundNode.cpp b/Grinder/ui/image/LayerBackgroundNode.cpp
new file mode 100644
index 0000000..be9e535
--- /dev/null
+++ b/Grinder/ui/image/LayerBackgroundNode.cpp
@@ -0,0 +1,67 @@
+/******************************************************************************
+ * File: LayerBackgroundNode.cpp
+ * Date: 15.6.2018
+ *****************************************************************************/
+
+#include "Grinder.h"
+#include "LayerBackgroundNode.h"
+#include "image/Layer.h"
+#include "project/ImageReference.h"
+#include "util/CVUtils.h"
+#include "res/Resources.h"
+
+LayerBackgroundNode::LayerBackgroundNode(ImageEditorScene* scene, Layer* layer, QGraphicsItem* parent) : LayerItemNode(scene, layer, parent),
+	_imageNode{new QGraphicsPixmapItem{this}}
+{
+	setFlags(0);	// This item doesn't behave like an item at all
+
+	// Set up the background node
+	_imageNode->setPos(QPointF{0.0, 0.0});
+	_imageNode->setShapeMode(QGraphicsPixmapItem::BoundingRectShape);
+	_imageNode->setTransformationMode(Qt::SmoothTransformation);
+
+	refreshImage();
+
+	updateZOrder();
+	updateVisibility();
+	updateAlpha();
+
+	updateNode();
+}
+
+void LayerBackgroundNode::refreshImage()
+{
+	bool resetPixmap = true;
+
+	if (auto backgroundImage = _layer->backgroundImage())
+	{
+		try {
+			auto imageData = backgroundImage->loadImage();
+			auto image = CVUtils::matrixToPixmap(imageData);
+
+			if (!image.isNull())
+			{
+				_imageNode->setPixmap(image);
+				resetPixmap = false;
+			}
+		} catch (...) {
+			// Just ignore any errors
+		}
+	}
+
+	if (resetPixmap)
+		_imageNode->setPixmap(QPixmap{FILE_ICON_EDITOR_NOIMAGE});	// If the image is invalid, show a question mark indicating an error
+
+	updateNode();
+}
+
+void LayerBackgroundNode::updateZOrder()
+{
+	setZValue(_layer->getZOrder() - 0.5);	// Place the background just below the layer pixels
+}
+
+void LayerBackgroundNode::updateGeometry()
+{
+	_nodeRect = _nodeRectSelected = _imageNode->boundingRect();
+	ImageEditorNode::updateGeometry();
+}
diff --git a/Grinder/ui/image/LayerBackgroundNode.h b/Grinder/ui/image/LayerBackgroundNode.h
new file mode 100644
index 0000000..d2cab9c
--- /dev/null
+++ b/Grinder/ui/image/LayerBackgroundNode.h
@@ -0,0 +1,36 @@
+/******************************************************************************
+ * File: LayerBackgroundNode.h
+ * Date: 15.6.2018
+ *****************************************************************************/
+
+#ifndef LAYERBACKGROUNDNODE_H
+#define LAYERBACKGROUNDNODE_H
+
+#include "LayerItemNode.h"
+
+namespace grndr
+{
+	class LayerPixels;
+
+	class LayerBackgroundNode : public LayerItemNode
+	{
+		Q_OBJECT
+
+	public:
+		LayerBackgroundNode(ImageEditorScene* scene, Layer* layer, QGraphicsItem* parent = nullptr);
+
+	public:
+		void refreshImage();
+
+	public slots:
+		virtual void updateZOrder() override;
+
+	protected:
+		virtual void updateGeometry() override;
+
+	private:
+		QGraphicsPixmapItem* _imageNode{nullptr};
+	};
+}
+
+#endif
diff --git a/Grinder/ui/image/LayerItemNode.cpp b/Grinder/ui/image/LayerItemNode.cpp
new file mode 100644
index 0000000..9714418
--- /dev/null
+++ b/Grinder/ui/image/LayerItemNode.cpp
@@ -0,0 +1,38 @@
+/******************************************************************************
+ * File: LayerItemNode.cpp
+ * Date: 15.6.2018
+ *****************************************************************************/
+
+#include "Grinder.h"
+#include "LayerItemNode.h"
+#include "image/Layer.h"
+
+LayerItemNode::LayerItemNode(ImageEditorScene* scene, Layer* layer, QGraphicsItem* parent) : ImageEditorNode(scene, parent),
+	_layer{layer}
+{
+	if (!layer)
+		throw std::invalid_argument{_EXCPT("layer may not be null")};
+}
+
+void LayerItemNode::updateNode()
+{
+	prepareGeometryChange();
+	updateGeometry();
+
+	update();
+}
+
+void LayerItemNode::updateZOrder()
+{
+	setZValue(_layer->getZOrder());	// Use the z-order of the layer
+}
+
+void LayerItemNode::updateVisibility()
+{
+	setVisible(_layer->isVisible());	// Use the visibility flag of the layer
+}
+
+void LayerItemNode::updateAlpha()
+{
+	setOpacity(_layer->getAlpha() / 100.0f);	// Use the alpha value of the layer
+}
diff --git a/Grinder/ui/image/LayerItemNode.h b/Grinder/ui/image/LayerItemNode.h
new file mode 100644
index 0000000..d5ab0e9
--- /dev/null
+++ b/Grinder/ui/image/LayerItemNode.h
@@ -0,0 +1,38 @@
+/******************************************************************************
+ * File: LayerItemNode.h
+ * Date: 15.6.2018
+ *****************************************************************************/
+
+#ifndef LAYERITEMNODE_H
+#define LAYERITEMNODE_H
+
+#include "ImageEditorNode.h"
+
+namespace grndr
+{
+	class Layer;
+
+	class LayerItemNode : public ImageEditorNode
+	{
+		Q_OBJECT
+
+	public:
+		LayerItemNode(ImageEditorScene* scene, Layer* layer, QGraphicsItem* parent = nullptr);
+
+	public:
+		Layer* layer() { return _layer; }
+		const Layer* layer() const { return _layer; }
+
+	public slots:
+		virtual void updateNode();
+
+		virtual void updateZOrder();
+		virtual void updateVisibility();
+		virtual void updateAlpha();
+
+	protected:
+		Layer* _layer{nullptr};
+	};
+}
+
+#endif
diff --git a/Grinder/ui/image/LayerPixelsNode.cpp b/Grinder/ui/image/LayerPixelsNode.cpp
index 1cf793a..ca87a44 100644
--- a/Grinder/ui/image/LayerPixelsNode.cpp
+++ b/Grinder/ui/image/LayerPixelsNode.cpp
@@ -8,50 +8,30 @@
 #include "ImageEditorStyle.h"
 #include "image/Layer.h"
 
-LayerPixelsNode::LayerPixelsNode(ImageEditorScene* scene, const std::shared_ptr<Layer>& layer, QGraphicsItem* parent) : ImageEditorNode(scene, parent),
-	_layer{layer}
+LayerPixelsNode::LayerPixelsNode(ImageEditorScene* scene, Layer* layer, QGraphicsItem* parent) : LayerItemNode(scene, layer, parent)
 {
 	setFlags(0);	// This item doesn't behave like an item at all
 
-	if (auto layer = _layer.lock())	// Make sure that the underlying layer still exists
-	{
-		// Create the renderer for the layer pixels
-		_renderer = layer->layerPixels().createRenderer(_style.getItemNodeStyle());
+	// Create the renderer for the layer pixels
+	_renderer = layer->layerPixels().createRenderer(_style.getItemNodeStyle());
 
-		// Update the node whenever the pixels data has changed
-		connect(&layer->layerPixels().data(), &LayerPixelsData::dataModified, this, &LayerPixelsNode::updateNode);
-	}
+	// Update the node whenever the pixels data has changed
+	connect(&layer->layerPixels().data(), &LayerPixelsData::dataModified, this, &LayerPixelsNode::updateNode);
 
 	updateZOrder();
 	updateVisibility();
+	updateAlpha();
 
 	updateNode();
 }
 
-void LayerPixelsNode::updateNode()
-{
-	prepareGeometryChange();
-	updateGeometry();
-
-	update();
-}
-
 void LayerPixelsNode::updateZOrder()
 {
-	if (auto layer = _layer.lock())	// Make sure that the underlying layer still exists
-		setZValue(layer->getZOrder() - 0.1);	// Place the pixels just below all other items
-}
-
-void LayerPixelsNode::updateVisibility()
-{
-	if (auto layer = _layer.lock())	// Make sure that the underlying layer still exists
-		setVisible(layer->isVisible());	// Use the visibility flag of the layer
+	setZValue(_layer->getZOrder() - 0.1);	// Place the pixels just below all other items
 }
 
 void LayerPixelsNode::updateGeometry()
 {
-	if (auto layer = _layer.lock())	// Make sure that the underlying layer still exists
-		_nodeRect = _nodeRectSelected = layer->layerPixels().data().bounds();
-
+	_nodeRect = _nodeRectSelected = _layer->layerPixels().data().bounds();
 	ImageEditorNode::updateGeometry();
 }
diff --git a/Grinder/ui/image/LayerPixelsNode.h b/Grinder/ui/image/LayerPixelsNode.h
index b1fe997..d6f176b 100644
--- a/Grinder/ui/image/LayerPixelsNode.h
+++ b/Grinder/ui/image/LayerPixelsNode.h
@@ -6,35 +6,24 @@
 #ifndef LAYERPIXELSNODE_H
 #define LAYERPIXELSNODE_H
 
-#include "ImageEditorNode.h"
+#include "LayerItemNode.h"
 
 namespace grndr
 {
-	class Layer;
 	class LayerPixels;
 
-	class LayerPixelsNode : public ImageEditorNode
+	class LayerPixelsNode : public LayerItemNode
 	{
 		Q_OBJECT
 
 	public:
-		LayerPixelsNode(ImageEditorScene* scene, const std::shared_ptr<Layer>& layer, QGraphicsItem* parent = nullptr);
-
-	public:
-		std::weak_ptr<Layer>& layer() { return _layer; }
-		const std::weak_ptr<Layer>& layer() const { return _layer; }
+		LayerPixelsNode(ImageEditorScene* scene, Layer* layer, QGraphicsItem* parent = nullptr);
 
 	public slots:
-		void updateNode();
-
-		void updateZOrder();
-		void updateVisibility();
+		virtual void updateZOrder() override;
 
 	protected:
 		virtual void updateGeometry() override;
-
-	private:
-		std::weak_ptr<Layer> _layer;
 	};
 }
 
diff --git a/Grinder/ui/image/LayersListItem.cpp b/Grinder/ui/image/LayersListItem.cpp
index f9acda4..eef43f9 100644
--- a/Grinder/ui/image/LayersListItem.cpp
+++ b/Grinder/ui/image/LayersListItem.cpp
@@ -9,6 +9,10 @@
 
 LayersListItem::LayersListItem(Layer* layer) : ActiveObjectListItem(layer)
 {
+	_specialFont.setItalic(true);
+	_specialActiveFont.setItalic(true);
+	_specialActiveFont.setBold(true);
+
 	setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable|Qt::ItemIsEditable|Qt::ItemIsUserCheckable);
 	setIcon(QIcon{FILE_ICON_LAYER});
 
@@ -19,6 +23,14 @@ void LayersListItem::updateItem()
 {
 	ActiveObjectListItem::updateItem();
 
+	if (getType() != Layer::Type::Standard)
+	{
+		if (_isActive)
+			this->setFont(_specialActiveFont);
+		else
+			this->setFont(_specialFont);
+	}
+
 	setText(getName());
 	setCheckState(isVisible() ? Qt::Checked : Qt::Unchecked);
 }
diff --git a/Grinder/ui/image/LayersListItem.h b/Grinder/ui/image/LayersListItem.h
index 7386912..0e6bd3d 100644
--- a/Grinder/ui/image/LayersListItem.h
+++ b/Grinder/ui/image/LayersListItem.h
@@ -21,7 +21,13 @@ namespace grndr
 
 	public:
 		QString getName() const { return _object->getName(); }
+		Layer::Type getType() const { return _object->getType(); }
 		bool isVisible() const { return _object->isVisible(); }
+		bool isRemovable() const { return _object->isRemovable(); }
+
+	private:
+		QFont _specialFont;
+		QFont _specialActiveFont;
 	};
 }
 
diff --git a/Grinder/ui/image/LayersListWidget.cpp b/Grinder/ui/image/LayersListWidget.cpp
index 5599081..73e6b2f 100644
--- a/Grinder/ui/image/LayersListWidget.cpp
+++ b/Grinder/ui/image/LayersListWidget.cpp
@@ -9,13 +9,15 @@
 #include "image/ImageBuild.h"
 #include "core/GrinderApplication.h"
 #include "ui/widgets/ControlBar.h"
+#include "ui/widgets/SliderValueWidget.h"
 #include "util/UIUtils.h"
 #include "util/StringUtils.h"
 #include "res/Resources.h"
 
-LayersListWidget::LayersListWidget(QWidget* parent) : MetaWidget(parent)
+LayersListWidget::LayersListWidget(QWidget* parent) : MetaWidget(parent),
+	_sliderValueWidget{new SliderValueWidget{"Opacity:", 0, 100, "%"}}
 {
-	// Create labels actions
+	// 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");
 	_moveDownAction = UIUtils::createAction(this, "Move &down", FILE_ICON_MOVEDOWN, SLOT(moveLayerDown()), "Move the selected layer down", "Ctrl+Shift+Down");
@@ -29,6 +31,14 @@ LayersListWidget::LayersListWidget(QWidget* parent) : MetaWidget(parent)
 	_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");
 
+	// Create the alpha slider action
+	auto sliderAction = new QWidgetAction{this};
+	sliderAction->setDefaultWidget(_sliderValueWidget);
+	_alphaAction = sliderAction;
+
+	// Update the layer opacity (alpha) if the value has been changed
+	connect(_sliderValueWidget, &SliderValueWidget::valueChanged, this, &LayersListWidget::layerOpacityChanged);
+
 	_moveUpAction->setData(Qt::ToolButtonIconOnly);
 	_moveDownAction->setData(Qt::ToolButtonIconOnly);
 	_convertPixelsToItemsAction->setData(Qt::ToolButtonIconOnly);
@@ -101,16 +111,24 @@ std::vector<QAction*> LayersListWidget::getActions(MetaWidget::AddActionsMode mo
 
 	actions.push_back(_newLayerAction);	
 	actions.push_back(_removeLayerAction);
-	actions.push_back(nullptr);
+	actions.push_back(nullptr);	
+
+	if (mode == AddActionsMode::ContextMenu)
+	{
+		actions.push_back(_alphaAction);
+		actions.push_back(nullptr);
+	}
+
 	actions.push_back(_convertPixelsToItemsAction);
 	actions.push_back(nullptr);
+
 	actions.push_back(_moveUpAction);
 	actions.push_back(_moveDownAction);
 
 	if (mode == AddActionsMode::ContextMenu)
 	{
 		actions.push_back(nullptr);
-		actions.push_back(_removeAllLayersAction);
+		actions.push_back(_removeAllLayersAction);		
 	}
 
 	return actions;
@@ -219,6 +237,12 @@ void LayersListWidget::layerCheckChanged(QListWidgetItem* item) const
 		_imageEditor->controller().setLayerVisibility(layerItem->object(), item->checkState() == Qt::Checked);
 }
 
+void LayersListWidget::layerOpacityChanged(int value) const
+{
+	if (auto layer = currentObject())
+		_imageEditor->controller().setLayerAlpha(layer, value);
+}
+
 void LayersListWidget::layerSwitching(Layer* layer)
 {
 	if (layer)
@@ -262,6 +286,13 @@ void LayersListWidget::updateActions()
 {
 	auto currentLayer = currentObject();
 	bool layerSelected = (currentLayer != nullptr);
+	int removableLayersCount = 0;
+
+	for (int i = 0; i < count(); ++i)
+	{
+		if (objectItem(i)->isRemovable())
+			++removableLayersCount;
+	}
 
 	_renameLayerAction->setEnabled(layerSelected);
 	_moveUpAction->setEnabled(layerSelected && currentRow() > 0);
@@ -269,7 +300,9 @@ void LayersListWidget::updateActions()
 	_convertPixelsToItemsAction->setEnabled(layerSelected && !currentLayer->layerPixels().data().empty());
 	_copyLayer->setEnabled(layerSelected);
 	_pasteLayer->setEnabled(grinder()->clipboardManager().hasData(LayerVector::Serialization_Element));
-	_cutLayer->setEnabled(layerSelected);
-	_removeLayerAction->setEnabled(layerSelected);
-	_removeAllLayersAction->setEnabled(count() > 0);
+	_cutLayer->setEnabled(layerSelected && currentLayer->isRemovable());
+	_removeLayerAction->setEnabled(layerSelected && currentLayer->isRemovable());
+	_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 3094b6b..3cae37b 100644
--- a/Grinder/ui/image/LayersListWidget.h
+++ b/Grinder/ui/image/LayersListWidget.h
@@ -14,6 +14,7 @@
 namespace grndr
 {
 	class Layer;
+	class SliderValueWidget;
 
 	using LayerObjectListWidget = ActiveObjectListWidget<Layer, LayersListItem>;
 
@@ -53,6 +54,7 @@ namespace grndr
 		void imageBuildSwitched(ImageBuild* imageBuild);
 
 		void layerCheckChanged(QListWidgetItem* item) const;
+		void layerOpacityChanged(int value) const;
 		void layerSwitching(Layer* layer);
 		void layerSwitched(Layer* layer);
 		void layerRenamed(QWidget* editor) const;
@@ -61,6 +63,8 @@ namespace grndr
 		void updateActions();
 
 	private:
+		SliderValueWidget* _sliderValueWidget{nullptr};
+
 		QAction* _renameLayerAction{nullptr};
 		QAction* _newLayerAction{nullptr};
 		QAction* _moveUpAction{nullptr};
@@ -71,6 +75,7 @@ namespace grndr
 		QAction* _cutLayer{nullptr};
 		QAction* _removeLayerAction{nullptr};
 		QAction* _removeAllLayersAction{nullptr};
+		QAction* _alphaAction{nullptr};
 	};
 }
 
diff --git a/Grinder/ui/widgets/SliderValueWidget.cpp b/Grinder/ui/widgets/SliderValueWidget.cpp
new file mode 100644
index 0000000..ea216df
--- /dev/null
+++ b/Grinder/ui/widgets/SliderValueWidget.cpp
@@ -0,0 +1,56 @@
+/******************************************************************************
+ * File: SliderValueWidget.cpp
+ * Date: 15.6.2018
+ *****************************************************************************/
+
+#include "Grinder.h"
+#include "SliderValueWidget.h"
+#include "core/GrinderApplication.h"
+
+SliderValueWidget::SliderValueWidget(QString label, int minValue, int maxValue, QString valueSuffix, QWidget* parent) : QWidget(parent),
+	_layout{new QHBoxLayout{this}}
+{
+	_layout->setContentsMargins(4, 2, 4, 2);
+	_layout->setSpacing(6);
+
+	_slider = new QSlider{Qt::Horizontal};
+	_slider->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+	_slider->setRange(minValue, maxValue);
+	_slider->setTickInterval((maxValue - minValue) / 20);
+	_slider->setTickPosition(QSlider::TicksBelow);
+
+	_spinBox = new QSpinBox{};
+	_spinBox->setRange(minValue, maxValue);
+	_spinBox->setSuffix(valueSuffix);
+
+	connect(_slider, &QSlider::valueChanged, this, &SliderValueWidget::sliderValueChanged);
+	connect(_spinBox, QOverload<int>::of(&QSpinBox::valueChanged), this, &SliderValueWidget::spinBoxValueChanged);
+
+	if (!label.isEmpty())
+	{
+		auto labelWidget = new QLabel{label};
+		labelWidget->setFont(QApplication::font("QMenu"));
+		_layout->addWidget(labelWidget);
+	}
+
+	_layout->addWidget(_slider);
+	_layout->addWidget(_spinBox);
+}
+
+void SliderValueWidget::sliderValueChanged(int value)
+{
+	if (_spinBox->value() != value)
+	{
+		_spinBox->setValue(value);
+		emit valueChanged(value);
+	}
+}
+
+void SliderValueWidget::spinBoxValueChanged(int value)
+{
+	if (_slider->value() != value)
+	{
+		_slider->setValue(value);
+		emit valueChanged(value);
+	}
+}
diff --git a/Grinder/ui/widgets/SliderValueWidget.h b/Grinder/ui/widgets/SliderValueWidget.h
new file mode 100644
index 0000000..b59949c
--- /dev/null
+++ b/Grinder/ui/widgets/SliderValueWidget.h
@@ -0,0 +1,41 @@
+/******************************************************************************
+ * File: SliderValueWidget.h
+ * Date: 15.6.2018
+ *****************************************************************************/
+
+#ifndef SLIDERVALUEWIDGET_H
+#define SLIDERVALUEWIDGET_H
+
+#include <QHBoxLayout>
+#include <QSlider>
+#include <QSpinBox>
+
+namespace grndr
+{
+	class SliderValueWidget : public QWidget
+	{
+		Q_OBJECT
+
+	public:
+		SliderValueWidget(QString label, int minValue, int maxValue, QString valueSuffix = "", QWidget* parent = nullptr);
+
+	public:
+		int getValue() const { return _slider->value(); }
+		void setValue(int value) { _slider->setValue(value); }
+
+	signals:
+		void valueChanged(int);
+
+	private slots:
+		void sliderValueChanged(int value);
+		void spinBoxValueChanged(int value);
+
+	private:
+		QHBoxLayout* _layout{nullptr};
+
+		QSlider* _slider{nullptr};
+		QSpinBox* _spinBox{nullptr};
+	};
+}
+
+#endif
-- 
GitLab