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