diff --git a/Grinder/Version.h b/Grinder/Version.h index bca1fccb125e94cf4fd85c982fb9aaa3bd9c6c0d..b68f9ad20739bedd097682db2564b1cc4b4861fc 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.10.2019" +#define GRNDR_INFO_DATE "28.10.2019" #define GRNDR_INFO_COMPANY "WWU Muenster" #define GRNDR_INFO_WEBSITE "http://www.uni-muenster.de" #define GRNDR_VERSION_MAJOR 0 #define GRNDR_VERSION_MINOR 16 #define GRNDR_VERSION_REVISION 0 -#define GRNDR_VERSION_BUILD 404 +#define GRNDR_VERSION_BUILD 407 namespace grndr { diff --git a/Grinder/controller/ImageEditorController.cpp b/Grinder/controller/ImageEditorController.cpp index 1be8674e46500384b01f1b0b318b661b8791e806..d837f731e26349770352243caf42d4b71b5d818c 100644 --- a/Grinder/controller/ImageEditorController.cpp +++ b/Grinder/controller/ImageEditorController.cpp @@ -9,6 +9,7 @@ #include "image/ImageTags.h" #include "image/ImageExceptions.h" #include "image/draftitems/PixelsDraftItem.h" +#include "cv/Pixel.h" #include "ui/image/ImageEditor.h" #include "ui/image/DraftItemNode.h" #include "ui/image/LayersListWidget.h" @@ -547,11 +548,43 @@ void ImageEditorController::convertSelectedPixelsToItem(Layer* layer, const Laye callControllerFunction("Converting selected pixels to an item", [this](Layer* layer, const LayerPixelsData::Selection& selection) { LongOperation opConvertPixels{"Converting selected pixels to an item"}; - auto selectedPixels = layer->layerPixels().data().getSelectedPixels(selection); + LayerPixelsData selectedPixels = layer->layerPixels().data().getSelectedPixels(selection); + + switch (_imageEditor->environment().getSamplingMode()) + { + case ImageEditorEnvironment::SamplingMode::Layer: + selectedPixels = layer->layerPixels().data().getSelectedPixels(selection); + break; + + case ImageEditorEnvironment::SamplingMode::Image: + selectedPixels = getSelectedImagePixels(selection); + break; + } if (!selectedPixels.empty()) { - clearSelectedPixels(layer, selection); + std::vector<Layer*> layers; + + switch (_imageEditor->environment().getSamplingMode()) + { + case ImageEditorEnvironment::SamplingMode::Layer: + layers.push_back(layer); + break; + + case ImageEditorEnvironment::SamplingMode::Image: + // Add all visible and editable layers + for (auto layer : _activeImageBuild->layers()) + { + if (layer->isVisible() && checkLayerEditability(layer.get(), false)) + layers.push_back(layer.get()); + } + + break; + } + + for (auto layer : layers) + clearSelectedPixels(layer, selection); + activateDefaultEditorTool(); if (auto draftItem = createDraftItem(DraftItemType::Pixels, layer)) @@ -643,9 +676,20 @@ void ImageEditorController::floodFillPixels(Layer* layer, QPoint seedPos, QColor if (layer && checkLayerEditability(layer)) { - callControllerFunction("Filling pixels", [](Layer* layer, QPoint seedPos, QColor color, float tolerance, bool perceivedDifference) { + callControllerFunction("Filling pixels", [this](Layer* layer, QPoint seedPos, QColor color, float tolerance, bool perceivedDifference) { layer->layerPixels().data().beginPainting(); - layer->layerPixels().data().floodFill(seedPos, color, tolerance, perceivedDifference); + + switch (_imageEditor->environment().getSamplingMode()) + { + case ImageEditorEnvironment::SamplingMode::Layer: + layer->layerPixels().data().floodFill(seedPos, color, tolerance, perceivedDifference); + break; + + case ImageEditorEnvironment::SamplingMode::Image: + layer->layerPixels().data().floodFill(seedPos, color, tolerance, perceivedDifference, _imageEditor->getDisplayedImage()); + break; + } + layer->layerPixels().data().endPainting(); return true; }, layer, seedPos, color, tolerance, perceivedDifference); @@ -879,6 +923,32 @@ void ImageEditorController::connectLayerSignals(Layer* layer, bool connectSignal disconnect(layer, nullptr, this, nullptr); } +LayerPixelsData ImageEditorController::getSelectedImagePixels(const LayerPixelsData::Selection& selection) const +{ + auto imageData = _imageEditor->getDisplayedImage(); + ConstPixelAccessor pixels{imageData}; + auto bounds = selection.bounds(); + auto checkBounds = [&imageData](int x, int y) { return x >= 0 && x < imageData.cols && y >= 0 && y < imageData.rows; }; + + if (checkBounds(bounds.left(), bounds.top()) && checkBounds(bounds.right(), bounds.bottom())) + { + LayerPixelsData selectedPixels{QSize{bounds.right() - bounds.left() + 1, bounds.bottom() - bounds.top() + 1}}; + + for (int y = bounds.top(); y <= bounds.bottom(); ++y) + { + for (int x = bounds.left(); x <= bounds.right(); ++x) + { + if (selection.isSet(x, y) && pixels.at(y, x).get().rgba() != 0) + selectedPixels.set(x - bounds.left(), y - bounds.top(), pixels.at(y, x)); + } + } + + return selectedPixels; + } + else + return {}; +} + void ImageEditorController::autoGenerateImageTag(ImageBuild* imageBuild, QColor color) const { if (!imageBuild) diff --git a/Grinder/controller/ImageEditorController.h b/Grinder/controller/ImageEditorController.h index 5ba2ac7f4e4462b5526165fd895e7baed7557583..e21183b844801bedeaaaad92f4fc06b3063d5f9a 100644 --- a/Grinder/controller/ImageEditorController.h +++ b/Grinder/controller/ImageEditorController.h @@ -119,6 +119,8 @@ namespace grndr void connectLayerSignals(Layer* layer, bool connectSignals = true) const; private: + LayerPixelsData getSelectedImagePixels(const LayerPixelsData::Selection& selection) const; + void autoGenerateImageTag(ImageBuild* imageBuild, QColor color) const; private slots: diff --git a/Grinder/cv/Pixel.h b/Grinder/cv/Pixel.h index 9abb99551dc7b349feb6dc9e2ed9ed2fefb6c345..a72ac3f8e33d233fd5b62e86943f7ec56f829ae0 100644 --- a/Grinder/cv/Pixel.h +++ b/Grinder/cv/Pixel.h @@ -1,234 +1,234 @@ -/****************************************************************************** - * File: Pixel.h - * Date: 19.6.2018 - *****************************************************************************/ - -#ifndef PIXEL_H -#define PIXEL_H - -#include <QColor> -#include <QPoint> -#include <opencv2/core.hpp> - -namespace grndr -{ - class ColorPixel - { - public: - ColorPixel() { } - ColorPixel(uint8_t r, uint8_t g, uint8_t b) : _b{b}, _g{g}, _r{r} { } - ColorPixel(const QColor& color) { setColor(color); } - ColorPixel(const ColorPixel& other) = default; - ColorPixel(ColorPixel&& other) = default; - - ColorPixel& operator =(const QColor& color) { setColor(color); return *this; } - - public: - QColor getColor() const; - void setColor(const QColor& color); - - operator QColor() const { return getColor(); } - - public: - bool operator ==(const ColorPixel& other) const; - bool operator ==(const QColor& other) const { return ColorPixel{other} == *this; } - - private: - uint8_t _b{0}; - uint8_t _g{0}; - uint8_t _r{0}; - }; - - class ColorAlphaPixel - { - public: - ColorAlphaPixel() { } - ColorAlphaPixel(uint8_t r, uint8_t g, uint8_t b, uint8_t a) : _b{b}, _g{g}, _r{r}, _a{a} { } - ColorAlphaPixel(const QColor& color) { setColor(color); } - ColorAlphaPixel(const ColorAlphaPixel& other) = default; - ColorAlphaPixel(ColorAlphaPixel&& other) = default; - - ColorAlphaPixel& operator =(const QColor& color) { setColor(color); return *this; } - - public: - QColor getColor() const; - void setColor(const QColor& color); - - operator QColor() const { return getColor(); } - - public: - bool operator ==(const ColorAlphaPixel& other) const; - bool operator ==(const QColor& other) const { return ColorAlphaPixel{other} == *this; } - - private: - uint8_t _b{0}; - uint8_t _g{0}; - uint8_t _r{0}; - uint8_t _a{0}; - }; - - class GrayscalePixel - { - public: - GrayscalePixel() { } - GrayscalePixel(uint8_t p) : _p{p} { } - GrayscalePixel(const QColor& color) { setColor(color); } - GrayscalePixel(const GrayscalePixel& other) = default; - GrayscalePixel(GrayscalePixel&& other) = default; - - GrayscalePixel& operator =(uint8_t p) { _p = p; return *this; } - GrayscalePixel& operator =(const QColor& color) { setColor(color); return *this; } - GrayscalePixel& operator =(const GrayscalePixel& other) = default; - GrayscalePixel& operator =(GrayscalePixel&& other) = default; - - public: - QColor getColor() const; - void setColor(const QColor& color); - - operator uint8_t() const { return _p; } - operator QColor() const { return getColor(); } - - public: - bool operator ==(const GrayscalePixel& other) const { return _p == other._p; } - bool operator ==(const QColor& other) const { return GrayscalePixel{other} == *this; } - - private: - uint8_t _p{0}; - }; - - template<bool ConstAccess> - class PixelRef - { - public: - using color_pixel_type = std::conditional_t<ConstAccess, const ColorPixel, ColorPixel>; - using coloralpha_pixel_type = std::conditional_t<ConstAccess, const ColorAlphaPixel, ColorAlphaPixel>; - using grayscale_pixel_type = std::conditional_t<ConstAccess, const GrayscalePixel, GrayscalePixel>; - - public: - PixelRef(color_pixel_type* pixel) : _pixel{pixel} { } - PixelRef(coloralpha_pixel_type* pixel) : _alphaPixel{pixel} { } - PixelRef(grayscale_pixel_type* grayscalePixel) : _grayscalePixel{grayscalePixel} { } - - public: - template<bool hasWriteAccess = !ConstAccess> - std::enable_if_t<hasWriteAccess, PixelRef&> operator =(const QColor& color) { set(color); return *this; } - - public: - QColor get() const; - template<bool hasWriteAccess = !ConstAccess> - std::enable_if_t<hasWriteAccess, void> set(const QColor& color); - - operator QColor() const { return get(); } - - private: - color_pixel_type* _pixel{nullptr}; - coloralpha_pixel_type* _alphaPixel{nullptr}; - grayscale_pixel_type* _grayscalePixel{nullptr}; - }; - - template<bool ConstAccess> - class GenericPixelAccessor - { - public: - using matrix_type = std::conditional_t<ConstAccess, const cv::Mat, cv::Mat>; - using pixel_ref_type = PixelRef<ConstAccess>; - - public: - GenericPixelAccessor(matrix_type& matrix); - - public: - pixel_ref_type at(int r, int c); - pixel_ref_type at(QPoint pos) { return at(pos.y(), pos.x()); } - void forEach(std::function<void(pixel_ref_type, QPoint)> callback); - - auto operator [](QPoint pos) { return at(pos); } - - private: - void verifyMatrix(); - - private: - matrix_type& _matrix; - }; - - using PixelAccessor = GenericPixelAccessor<false>; - using ConstPixelAccessor = GenericPixelAccessor<true>; -} - -namespace cv -{ - template<> - class DataType<grndr::ColorPixel> - { - public: - using value_type = grndr::ColorPixel; - using work_type = typename cv::DataType<uint8_t>::work_type; - using channel_type = uint8_t; - - enum - { - generic_type = 0, - channels = 3, - fmt = cv::traits::SafeFmt<channel_type>::fmt + ((channels - 1) << 8) - }; - - using vec_type = Vec<channel_type, channels>; - }; - - template<> - class DataType<grndr::ColorAlphaPixel> - { - public: - using value_type = grndr::ColorAlphaPixel; - using work_type = typename cv::DataType<uint8_t>::work_type; - using channel_type = uint8_t; - - enum - { - generic_type = 0, - channels = 4, - fmt = cv::traits::SafeFmt<channel_type>::fmt + ((channels - 1) << 8) - }; - - using vec_type = Vec<channel_type, channels>; - }; - - template<> - class DataType<grndr::GrayscalePixel> - { - public: - using value_type = grndr::GrayscalePixel; - using work_type = typename cv::DataType<uint8_t>::work_type; - using channel_type = uint8_t; - - enum - { - generic_type = 0, - channels = 1, - fmt = cv::traits::SafeFmt<channel_type>::fmt + ((channels - 1) << 8) - }; - - using vec_type = Vec<channel_type, channels>; - }; - - namespace traits - { - template<> - struct Depth<grndr::ColorPixel> { enum { value = Depth<uint8_t>::value }; }; - template<> - struct Type<grndr::ColorPixel> { enum { value = CV_MAKETYPE(Depth<uint8_t>::value, 3) }; }; - - template<> - struct Depth<grndr::ColorAlphaPixel> { enum { value = Depth<uint8_t>::value }; }; - template<> - struct Type<grndr::ColorAlphaPixel> { enum { value = CV_MAKETYPE(Depth<uint8_t>::value, 4) }; }; - - template<> - struct Depth<grndr::GrayscalePixel> { enum { value = Depth<uint8_t>::value }; }; - template<> - struct Type<grndr::GrayscalePixel> { enum { value = CV_MAKETYPE(Depth<uint8_t>::value, 1) }; }; - } -} - -#include "Pixel.impl.h" - -#endif +/****************************************************************************** + * File: Pixel.h + * Date: 19.6.2018 + *****************************************************************************/ + +#ifndef PIXEL_H +#define PIXEL_H + +#include <QColor> +#include <QPoint> +#include <opencv2/core.hpp> + +namespace grndr +{ + class ColorPixel + { + public: + ColorPixel() { } + ColorPixel(uint8_t r, uint8_t g, uint8_t b) : _b{b}, _g{g}, _r{r} { } + ColorPixel(const QColor& color) { setColor(color); } + ColorPixel(const ColorPixel& other) = default; + ColorPixel(ColorPixel&& other) = default; + + ColorPixel& operator =(const QColor& color) { setColor(color); return *this; } + + public: + QColor getColor() const; + void setColor(const QColor& color); + + operator QColor() const { return getColor(); } + + public: + bool operator ==(const ColorPixel& other) const; + bool operator ==(const QColor& other) const { return ColorPixel{other} == *this; } + + private: + uint8_t _b{0}; + uint8_t _g{0}; + uint8_t _r{0}; + }; + + class ColorAlphaPixel + { + public: + ColorAlphaPixel() { } + ColorAlphaPixel(uint8_t r, uint8_t g, uint8_t b, uint8_t a) : _b{b}, _g{g}, _r{r}, _a{a} { } + ColorAlphaPixel(const QColor& color) { setColor(color); } + ColorAlphaPixel(const ColorAlphaPixel& other) = default; + ColorAlphaPixel(ColorAlphaPixel&& other) = default; + + ColorAlphaPixel& operator =(const QColor& color) { setColor(color); return *this; } + + public: + QColor getColor() const; + void setColor(const QColor& color); + + operator QColor() const { return getColor(); } + + public: + bool operator ==(const ColorAlphaPixel& other) const; + bool operator ==(const QColor& other) const { return ColorAlphaPixel{other} == *this; } + + private: + uint8_t _b{0}; + uint8_t _g{0}; + uint8_t _r{0}; + uint8_t _a{0}; + }; + + class GrayscalePixel + { + public: + GrayscalePixel() { } + GrayscalePixel(uint8_t p) : _p{p} { } + GrayscalePixel(const QColor& color) { setColor(color); } + GrayscalePixel(const GrayscalePixel& other) = default; + GrayscalePixel(GrayscalePixel&& other) = default; + + GrayscalePixel& operator =(uint8_t p) { _p = p; return *this; } + GrayscalePixel& operator =(const QColor& color) { setColor(color); return *this; } + GrayscalePixel& operator =(const GrayscalePixel& other) = default; + GrayscalePixel& operator =(GrayscalePixel&& other) = default; + + public: + QColor getColor() const; + void setColor(const QColor& color); + + operator uint8_t() const { return _p; } + operator QColor() const { return getColor(); } + + public: + bool operator ==(const GrayscalePixel& other) const { return _p == other._p; } + bool operator ==(const QColor& other) const { return GrayscalePixel{other} == *this; } + + private: + uint8_t _p{0}; + }; + + template<bool ConstAccess> + class PixelRef + { + public: + using color_pixel_type = std::conditional_t<ConstAccess, const ColorPixel, ColorPixel>; + using coloralpha_pixel_type = std::conditional_t<ConstAccess, const ColorAlphaPixel, ColorAlphaPixel>; + using grayscale_pixel_type = std::conditional_t<ConstAccess, const GrayscalePixel, GrayscalePixel>; + + public: + PixelRef(color_pixel_type* pixel) : _pixel{pixel} { } + PixelRef(coloralpha_pixel_type* pixel) : _alphaPixel{pixel} { } + PixelRef(grayscale_pixel_type* grayscalePixel) : _grayscalePixel{grayscalePixel} { } + + public: + template<bool hasWriteAccess = !ConstAccess> + std::enable_if_t<hasWriteAccess, PixelRef&> operator =(const QColor& color) { set(color); return *this; } + + public: + QColor get() const; + template<bool hasWriteAccess = !ConstAccess> + std::enable_if_t<hasWriteAccess, void> set(const QColor& color); + + operator QColor() const { return get(); } + + private: + color_pixel_type* _pixel{nullptr}; + coloralpha_pixel_type* _alphaPixel{nullptr}; + grayscale_pixel_type* _grayscalePixel{nullptr}; + }; + + template<bool ConstAccess> + class GenericPixelAccessor + { + public: + using matrix_type = std::conditional_t<ConstAccess, const cv::Mat, cv::Mat>; + using pixel_ref_type = PixelRef<ConstAccess>; + + public: + GenericPixelAccessor(matrix_type& matrix); + + public: + pixel_ref_type at(int r, int c); + pixel_ref_type at(QPoint pos) { return at(pos.y(), pos.x()); } + void forEach(std::function<void(pixel_ref_type, QPoint)> callback); + + auto operator [](QPoint pos) { return at(pos); } + + private: + void verifyMatrix(); + + private: + matrix_type& _matrix; + }; + + using PixelAccessor = GenericPixelAccessor<false>; + using ConstPixelAccessor = GenericPixelAccessor<true>; +} + +namespace cv +{ + template<> + class DataType<grndr::ColorPixel> + { + public: + using value_type = grndr::ColorPixel; + using work_type = typename cv::DataType<uint8_t>::work_type; + using channel_type = uint8_t; + + enum + { + generic_type = 0, + channels = 3, + fmt = cv::traits::SafeFmt<channel_type>::fmt + ((channels - 1) << 8) + }; + + using vec_type = Vec<channel_type, channels>; + }; + + template<> + class DataType<grndr::ColorAlphaPixel> + { + public: + using value_type = grndr::ColorAlphaPixel; + using work_type = typename cv::DataType<uint8_t>::work_type; + using channel_type = uint8_t; + + enum + { + generic_type = 0, + channels = 4, + fmt = cv::traits::SafeFmt<channel_type>::fmt + ((channels - 1) << 8) + }; + + using vec_type = Vec<channel_type, channels>; + }; + + template<> + class DataType<grndr::GrayscalePixel> + { + public: + using value_type = grndr::GrayscalePixel; + using work_type = typename cv::DataType<uint8_t>::work_type; + using channel_type = uint8_t; + + enum + { + generic_type = 0, + channels = 1, + fmt = cv::traits::SafeFmt<channel_type>::fmt + ((channels - 1) << 8) + }; + + using vec_type = Vec<channel_type, channels>; + }; + + namespace traits + { + template<> + struct Depth<grndr::ColorPixel> { enum { value = Depth<uint8_t>::value }; }; + template<> + struct Type<grndr::ColorPixel> { enum { value = CV_MAKETYPE(Depth<uint8_t>::value, 3) }; }; + + template<> + struct Depth<grndr::ColorAlphaPixel> { enum { value = Depth<uint8_t>::value }; }; + template<> + struct Type<grndr::ColorAlphaPixel> { enum { value = CV_MAKETYPE(Depth<uint8_t>::value, 4) }; }; + + template<> + struct Depth<grndr::GrayscalePixel> { enum { value = Depth<uint8_t>::value }; }; + template<> + struct Type<grndr::GrayscalePixel> { enum { value = CV_MAKETYPE(Depth<uint8_t>::value, 1) }; }; + } +} + +#include "Pixel.impl.h" + +#endif diff --git a/Grinder/cv/algorithms/FloodFill.cpp b/Grinder/cv/algorithms/FloodFill.cpp index 08c4d74e64ae73b51757e4b09a63da4108de7887..6d2b71722cfd6a6bc7584561bdbb6d5091bb423a 100644 --- a/Grinder/cv/algorithms/FloodFill.cpp +++ b/Grinder/cv/algorithms/FloodFill.cpp @@ -1,34 +1,32 @@ -/****************************************************************************** - * File: FloodFill.cpp - * Date: 13.7.2018 - *****************************************************************************/ - -#include "Grinder.h" -#include "FloodFill.h" -#include "cv/CVUtils.h" - -#include <stack> - -FloodFill::FloodFill(cv::Mat& matrix, QPoint pos, QColor fromColor, QColor toColor, float tolerance, bool perceivedDifference) : SeedFill(matrix, pos, fromColor, tolerance, perceivedDifference), - _toColor{toColor} -{ - -} - -void FloodFill::execute() -{ - if (_color == _toColor) - return; - - seedFill(_seedPos.x(), _seedPos.y()); - - // Colorize all filled pixels - for (int x = 0; x < _filledPixels.size().width(); ++x) - { - for (int y = 0; y < _filledPixels.size().height(); ++y) - { - if (_filledPixels.getFlag(x, y)) - _pixels.at(y, x) = _toColor.isValid() ? _toColor : QColor{0, 0, 0, 0}; - } - } -} +/****************************************************************************** + * File: FloodFill.cpp + * Date: 13.7.2018 + *****************************************************************************/ + +#include "Grinder.h" +#include "FloodFill.h" +#include "cv/CVUtils.h" + +FloodFill::FloodFill(const cv::Mat& sourceMatrix, cv::Mat& targetMatrix, QPoint pos, QColor fromColor, QColor toColor, float tolerance, bool perceivedDifference) : SeedFill(sourceMatrix, targetMatrix, pos, fromColor, tolerance, perceivedDifference), + _toColor{toColor} +{ + +} + +void FloodFill::execute() +{ + if (_color == _toColor) + return; + + seedFill(_seedPos.x(), _seedPos.y()); + + // Colorize all filled pixels + for (int x = 0; x < _filledPixels.size().width(); ++x) + { + for (int y = 0; y < _filledPixels.size().height(); ++y) + { + if (_filledPixels.getFlag(x, y)) + _targetPixels.at(y, x) = _toColor.isValid() ? _toColor : QColor{0, 0, 0, 0}; + } + } +} diff --git a/Grinder/cv/algorithms/FloodFill.h b/Grinder/cv/algorithms/FloodFill.h index cc0b41cf421098812291d4f0843a5a3396d0afa6..3fbbbb4e7311b503e6c4ae16600d96931f2f958b 100644 --- a/Grinder/cv/algorithms/FloodFill.h +++ b/Grinder/cv/algorithms/FloodFill.h @@ -1,30 +1,30 @@ -/****************************************************************************** - * File: FloodFill.h - * Date: 13.7.2018 - *****************************************************************************/ - -#ifndef FLOODFILL_H -#define FLOODFILL_H - -#include <QColor> -#include <QPoint> - -#include "SeedFill.h" -#include "cv/Pixel.h" - -namespace grndr -{ - class FloodFill : public SeedFill - { - public: - FloodFill(cv::Mat& matrix, QPoint pos, QColor fromColor, QColor toColor, float tolerance, bool perceivedDifference = true); - - public: - virtual void execute() override; - - private: - QColor _toColor; - }; -} - -#endif +/****************************************************************************** + * File: FloodFill.h + * Date: 13.7.2018 + *****************************************************************************/ + +#ifndef FLOODFILL_H +#define FLOODFILL_H + +#include <QColor> +#include <QPoint> + +#include "SeedFill.h" +#include "cv/Pixel.h" + +namespace grndr +{ + class FloodFill : public SeedFill + { + public: + FloodFill(const cv::Mat& sourceMatrix, cv::Mat& targetMatrix, QPoint pos, QColor fromColor, QColor toColor, float tolerance, bool perceivedDifference = true); + + public: + virtual void execute() override; + + private: + QColor _toColor; + }; +} + +#endif diff --git a/Grinder/cv/algorithms/SeedFill.cpp b/Grinder/cv/algorithms/SeedFill.cpp index 873469df415138ba22bbfd839769bfc6e3f5ad8e..5c8ec45d418ec6ba3cc9cb367ffe2a16c3ce20ca 100644 --- a/Grinder/cv/algorithms/SeedFill.cpp +++ b/Grinder/cv/algorithms/SeedFill.cpp @@ -1,114 +1,114 @@ -/****************************************************************************** - * File: SeedFill.cpp - * Date: 08.8.2018 - *****************************************************************************/ - -#include "Grinder.h" -#include "SeedFill.h" -#include "cv/CVUtils.h" - -#include <stack> - -SeedFill::SeedFill(cv::Mat& matrix, QPoint pos, QColor color, float tolerance, bool perceivedDifference) : CVAlgorithm(matrix), - _seedPos{pos}, _color{color}, _tolerance{tolerance}, _perceivedDifference{perceivedDifference}, _pixels{matrix} -{ - -} - -void SeedFill::execute() -{ - seedFill(_seedPos.x(), _seedPos.y()); -} - -void SeedFill::seedFill(int x, int y) -{ - _filledPixels.reset(QSize{_matrix.cols, _matrix.rows}); - - // Based on Paul Heckbert's Seed Fill Algorithm (crude & ugly, but reasonably fast) - std::stack<LineSegment> stack; - - auto push = [&stack, this](int y, int xl, int xr, int dy) { - if (y + dy >= 0 && y + dy < _matrix.rows) - stack.push(LineSegment{y, xl, xr, dy}); - }; - - auto checkColor = [this](int x, int y, QColor pixelColor) { - if (_filledPixels.isSet(x, y)) - return false; - - return ((_color.isValid() && CVUtils::compareColors(pixelColor, _color, _tolerance, _perceivedDifference)) || (!_color.isValid() && pixelColor.alpha() == 0)); - }; - - push(y, x, x, 1); - push(y + 1, x, x, -1); - - while (!stack.empty()) - { - auto lineSegment = stack.top(); - stack.pop(); - - int y = lineSegment.y + lineSegment.dy; - int l = 0; - - for (x = lineSegment.xl; x >= 0; --x) - { - auto pixel = _pixels.at(y, x); - auto pixelColor = pixel.get(); - - if (checkColor(x, y, pixelColor)) - _filledPixels.set(x, y); - else - break; - } - - bool skip = false; - - if (x >= lineSegment.xl) - skip = true; - - if (!skip) - { - l = x + 1; - - if (l < lineSegment.xl) - push(y, l, lineSegment.xl - 1, -lineSegment.dy); - - x = lineSegment.xl + 1; - } - - do - { - if (!skip) - { - for (; x < _matrix.cols; ++x) - { - auto pixel = _pixels.at(y, x); - auto pixelColor = pixel.get(); - - if (checkColor(x, y, pixelColor)) - _filledPixels.set(x, y); - else - break; - } - - push(y, l, x - 1, lineSegment.dy); - - if (x > lineSegment.xr + 1) - push(y, lineSegment.xr + 1, x - 1, -lineSegment.dy); - } - - skip = false; - - for (x++; x <= lineSegment.xr; ++x) - { - auto pixel = _pixels.at(y, x); - auto pixelColor = pixel.get(); - - if (checkColor(x, y, pixelColor)) - break; - } - - l = x; - } while (x <= lineSegment.xr); - } -} +/****************************************************************************** + * File: SeedFill.cpp + * Date: 08.8.2018 + *****************************************************************************/ + +#include "Grinder.h" +#include "SeedFill.h" +#include "cv/CVUtils.h" + +#include <stack> + +SeedFill::SeedFill(const cv::Mat& sourceMatrix, cv::Mat& targetMatrix, QPoint pos, QColor color, float tolerance, bool perceivedDifference) : CVAlgorithm(targetMatrix), + _sourceMatrix{sourceMatrix}, _sourcePixels{sourceMatrix}, _targetMatrix{targetMatrix}, _targetPixels{targetMatrix}, _seedPos{pos}, _color{color}, _tolerance{tolerance}, _perceivedDifference{perceivedDifference} +{ + +} + +void SeedFill::execute() +{ + seedFill(_seedPos.x(), _seedPos.y()); +} + +void SeedFill::seedFill(int x, int y) +{ + _filledPixels.reset(QSize{_targetMatrix.cols, _targetMatrix.rows}); + + // Based on Paul Heckbert's Seed Fill Algorithm (crude & ugly, but reasonably fast) + std::stack<LineSegment> stack; + + auto push = [&stack, this](int y, int xl, int xr, int dy) { + if (y + dy >= 0 && y + dy < _targetMatrix.rows) + stack.push(LineSegment{y, xl, xr, dy}); + }; + + auto checkColor = [this](int x, int y, QColor pixelColor) { + if (_filledPixels.isSet(x, y)) + return false; + + return ((_color.isValid() && CVUtils::compareColors(pixelColor, _color, _tolerance, _perceivedDifference)) || (!_color.isValid() && pixelColor.alpha() == 0)); + }; + + push(y, x, x, 1); + push(y + 1, x, x, -1); + + while (!stack.empty()) + { + auto lineSegment = stack.top(); + stack.pop(); + + int y = lineSegment.y + lineSegment.dy; + int l = 0; + + for (x = lineSegment.xl; x >= 0; --x) + { + auto pixel = _sourcePixels.at(y, x); + auto pixelColor = pixel.get(); + + if (checkColor(x, y, pixelColor)) + _filledPixels.set(x, y); + else + break; + } + + bool skip = false; + + if (x >= lineSegment.xl) + skip = true; + + if (!skip) + { + l = x + 1; + + if (l < lineSegment.xl) + push(y, l, lineSegment.xl - 1, -lineSegment.dy); + + x = lineSegment.xl + 1; + } + + do + { + if (!skip) + { + for (; x < _targetMatrix.cols; ++x) + { + auto pixel = _sourcePixels.at(y, x); + auto pixelColor = pixel.get(); + + if (checkColor(x, y, pixelColor)) + _filledPixels.set(x, y); + else + break; + } + + push(y, l, x - 1, lineSegment.dy); + + if (x > lineSegment.xr + 1) + push(y, lineSegment.xr + 1, x - 1, -lineSegment.dy); + } + + skip = false; + + for (x++; x <= lineSegment.xr; ++x) + { + auto pixel = _sourcePixels.at(y, x); + auto pixelColor = pixel.get(); + + if (checkColor(x, y, pixelColor)) + break; + } + + l = x; + } while (x <= lineSegment.xr); + } +} diff --git a/Grinder/cv/algorithms/SeedFill.h b/Grinder/cv/algorithms/SeedFill.h index afd03329295d6c390ef0c9cb8f62bcf1025ad1d5..52aac4d036071bbd815eb7c1b007dfc9fb5cc93d 100644 --- a/Grinder/cv/algorithms/SeedFill.h +++ b/Grinder/cv/algorithms/SeedFill.h @@ -1,47 +1,50 @@ -/****************************************************************************** - * File: SeedFill.h - * Date: 08.8.2018 - *****************************************************************************/ - -#ifndef SEEDFILL_H -#define SEEDFILL_H - -#include "CVAlgorithm.h" -#include "common/RangeMap.h" -#include "common/FlagMatrix.h" -#include "cv/Pixel.h" - -namespace grndr -{ - class SeedFill : public CVAlgorithm - { - public: - SeedFill(cv::Mat& matrix, QPoint pos, QColor color, float tolerance, bool perceivedDifference = true); - - public: - virtual void execute() override; - - public: - const FlagMatrix<>& filledPixels() const { return _filledPixels; } - - protected: - struct LineSegment - { - int y, xl, xr, dy; - }; - - void seedFill(int x, int y); - - protected: - QPoint _seedPos{0, 0}; - QColor _color; - float _tolerance{0.0f}; - bool _perceivedDifference{true}; - - PixelAccessor _pixels; - - FlagMatrix<> _filledPixels; - }; -} - -#endif +/****************************************************************************** + * File: SeedFill.h + * Date: 08.8.2018 + *****************************************************************************/ + +#ifndef SEEDFILL_H +#define SEEDFILL_H + +#include "CVAlgorithm.h" +#include "common/RangeMap.h" +#include "common/FlagMatrix.h" +#include "cv/Pixel.h" + +namespace grndr +{ + class SeedFill : public CVAlgorithm + { + public: + SeedFill(const cv::Mat& sourceMatrix, cv::Mat& targetMatrix, QPoint pos, QColor color, float tolerance, bool perceivedDifference = true); + + public: + virtual void execute() override; + + public: + const FlagMatrix<>& filledPixels() const { return _filledPixels; } + + protected: + struct LineSegment + { + int y, xl, xr, dy; + }; + + void seedFill(int x, int y); + + protected: + const cv::Mat& _sourceMatrix; + ConstPixelAccessor _sourcePixels; + cv::Mat& _targetMatrix; + PixelAccessor _targetPixels; + + QPoint _seedPos{0, 0}; + QColor _color; + float _tolerance{0.0f}; + bool _perceivedDifference{true}; + + FlagMatrix<> _filledPixels; + }; +} + +#endif diff --git a/Grinder/cv/algorithms/wand/ColorDistanceWandAlgorithm.cpp b/Grinder/cv/algorithms/wand/ColorDistanceWandAlgorithm.cpp index 9dd2855775598fbab7bc3da4620bda97ebb85508..b4b8edc98231b130ae209c9aeb3fde5619d5c3f3 100644 --- a/Grinder/cv/algorithms/wand/ColorDistanceWandAlgorithm.cpp +++ b/Grinder/cv/algorithms/wand/ColorDistanceWandAlgorithm.cpp @@ -10,13 +10,21 @@ const WandAlgorithm::AlgorithmType ColorDistanceWandAlgorithm::type_value = WandAlgorithm::AlgorithmType::ColorDistance; -LayerPixelsData::Selection ColorDistanceWandAlgorithm::execute(QPoint seed, const Layer& layer) +LayerPixelsData::Selection ColorDistanceWandAlgorithm::execute(QPoint seed, const Layer& layer, const cv::Mat& sourceImage) { const auto& layerPixels = layer.layerPixels().data(); auto pixelsMatrix = layerPixels.toMatrix(true); // Use the seed fill algorithm to get all pixels in the region around the seed position - SeedFill seedFill{pixelsMatrix, seed, layerPixels.get(seed), tolerance()->getRelativeValue(), *usePerceivedDistance()}; + QColor sourceColor = layerPixels.get(seed); + + if (!sourceImage.empty()) + { + ConstPixelAccessor pixels{sourceImage}; + sourceColor = pixels.at(seed); + } + + SeedFill seedFill{!sourceImage.empty() ? sourceImage : pixelsMatrix, pixelsMatrix, seed, sourceColor, tolerance()->getRelativeValue(), *usePerceivedDistance()}; seedFill.execute(); return seedFill.filledPixels(); } diff --git a/Grinder/cv/algorithms/wand/ColorDistanceWandAlgorithm.h b/Grinder/cv/algorithms/wand/ColorDistanceWandAlgorithm.h index eb7e560d1b5fc244045d6d44bdf35235653df7af..7b6b48a63f76319484f492a58744e6981e7f99c6 100644 --- a/Grinder/cv/algorithms/wand/ColorDistanceWandAlgorithm.h +++ b/Grinder/cv/algorithms/wand/ColorDistanceWandAlgorithm.h @@ -18,7 +18,7 @@ namespace grndr static const WandAlgorithm::AlgorithmType type_value; public: - virtual LayerPixelsData::Selection execute(QPoint seed, const Layer& layer) override; + virtual LayerPixelsData::Selection execute(QPoint seed, const Layer& layer, const cv::Mat& sourceImage) override; public: auto tolerance() { return dynamic_cast<PercentProperty*>(_tolerance.get()); } diff --git a/Grinder/cv/algorithms/wand/EdgesWandAlgorithm.cpp b/Grinder/cv/algorithms/wand/EdgesWandAlgorithm.cpp index b4debf2db6727056a774fe27af42e5a0a5a32de5..f4b8bea31765f045816cd608e5acb824c7c1637c 100644 --- a/Grinder/cv/algorithms/wand/EdgesWandAlgorithm.cpp +++ b/Grinder/cv/algorithms/wand/EdgesWandAlgorithm.cpp @@ -11,10 +11,15 @@ const WandAlgorithm::AlgorithmType EdgesWandAlgorithm::type_value = WandAlgorithm::AlgorithmType::Edges; -LayerPixelsData::Selection EdgesWandAlgorithm::execute(QPoint seed, const Layer& layer) +LayerPixelsData::Selection EdgesWandAlgorithm::execute(QPoint seed, const Layer& layer, const cv::Mat& sourceImage) { const auto& layerPixels = layer.layerPixels().data(); - auto pixelsMatrix = layerPixels.toMatrix(true); + cv::Mat pixelsMatrix; + + if (!sourceImage.empty()) + pixelsMatrix = sourceImage.clone(); + else + pixelsMatrix = layerPixels.toMatrix(true); // Use the Canny algorithm to detect edges cv::cvtColor(pixelsMatrix, pixelsMatrix, cv::COLOR_BGR2GRAY); @@ -38,7 +43,7 @@ LayerPixelsData::Selection EdgesWandAlgorithm::execute(QPoint seed, const Layer& // Get the contour under the seed position uint8_t edgeValue = contourRegions.at<uint8_t>(seed.y(), seed.x()); - LayerPixelsData::Selection selection{layer.getLayerSize()}; + LayerPixelsData::Selection selection{pixelsMatrix.cols, pixelsMatrix.rows}; for (int x = 0; x < contourRegions.cols; ++x) { diff --git a/Grinder/cv/algorithms/wand/EdgesWandAlgorithm.h b/Grinder/cv/algorithms/wand/EdgesWandAlgorithm.h index b8906e55d2643c0dda8d702e93dd467a66f0d071..2f501e07923a17dc9ac3f2e245cfa3446367afb3 100644 --- a/Grinder/cv/algorithms/wand/EdgesWandAlgorithm.h +++ b/Grinder/cv/algorithms/wand/EdgesWandAlgorithm.h @@ -1,38 +1,38 @@ -/****************************************************************************** - * File: EdgesWandAlgorithm.h - * Date: 08.8.2018 - *****************************************************************************/ - -#ifndef EDGESWANDALGORITHM_H -#define EDGESWANDALGORITHM_H - -#include "WandAlgorithm.h" - -namespace grndr -{ - class EdgesWandAlgorithm : public WandAlgorithm - { - Q_OBJECT - - public: - static const WandAlgorithm::AlgorithmType type_value; - - public: - virtual LayerPixelsData::Selection execute(QPoint seed, const Layer& layer) override; - - public: - auto thresholdLow() { return dynamic_cast<UIntProperty*>(_thresholdLow.get()); } - auto thresholdLow() const { return dynamic_cast<const UIntProperty*>(_thresholdLow.get()); } - auto thresholdHigh() { return dynamic_cast<UIntProperty*>(_thresholdHigh.get()); } - auto thresholdHigh() const { return dynamic_cast<const UIntProperty*>(_thresholdHigh.get()); } - - protected: - virtual void createProperties() override; - - private: - std::shared_ptr<PropertyBase> _thresholdLow; - std::shared_ptr<PropertyBase> _thresholdHigh; - }; -} - -#endif +/****************************************************************************** + * File: EdgesWandAlgorithm.h + * Date: 08.8.2018 + *****************************************************************************/ + +#ifndef EDGESWANDALGORITHM_H +#define EDGESWANDALGORITHM_H + +#include "WandAlgorithm.h" + +namespace grndr +{ + class EdgesWandAlgorithm : public WandAlgorithm + { + Q_OBJECT + + public: + static const WandAlgorithm::AlgorithmType type_value; + + public: + virtual LayerPixelsData::Selection execute(QPoint seed, const Layer& layer, const cv::Mat& sourceImage) override; + + public: + auto thresholdLow() { return dynamic_cast<UIntProperty*>(_thresholdLow.get()); } + auto thresholdLow() const { return dynamic_cast<const UIntProperty*>(_thresholdLow.get()); } + auto thresholdHigh() { return dynamic_cast<UIntProperty*>(_thresholdHigh.get()); } + auto thresholdHigh() const { return dynamic_cast<const UIntProperty*>(_thresholdHigh.get()); } + + protected: + virtual void createProperties() override; + + private: + std::shared_ptr<PropertyBase> _thresholdLow; + std::shared_ptr<PropertyBase> _thresholdHigh; + }; +} + +#endif diff --git a/Grinder/cv/algorithms/wand/WandAlgorithm.h b/Grinder/cv/algorithms/wand/WandAlgorithm.h index b720ee4529f013272365d616abeb478087a23df6..72009df54bb3dcf7986bba7f286741584a88c598 100644 --- a/Grinder/cv/algorithms/wand/WandAlgorithm.h +++ b/Grinder/cv/algorithms/wand/WandAlgorithm.h @@ -1,34 +1,34 @@ -/****************************************************************************** - * File: WandAlgorithm.h - * Date: 07.8.2018 - *****************************************************************************/ - -#ifndef WANDALGORITHM_H -#define WANDALGORITHM_H - -#include "common/properties/PropertyObject.h" -#include "image/Layer.h" - -namespace grndr -{ - class WandAlgorithm : public PropertyObject - { - Q_OBJECT - - public: - enum class AlgorithmType - { - ColorDistance, - Edges, - Watershed, - }; - - public: - virtual void initWandAlgorithm(); - - public: - virtual LayerPixelsData::Selection execute(QPoint seed, const Layer& layer) = 0; - }; -} - -#endif +/****************************************************************************** + * File: WandAlgorithm.h + * Date: 07.8.2018 + *****************************************************************************/ + +#ifndef WANDALGORITHM_H +#define WANDALGORITHM_H + +#include "common/properties/PropertyObject.h" +#include "image/Layer.h" + +namespace grndr +{ + class WandAlgorithm : public PropertyObject + { + Q_OBJECT + + public: + enum class AlgorithmType + { + ColorDistance, + Edges, + Watershed, + }; + + public: + virtual void initWandAlgorithm(); + + public: + virtual LayerPixelsData::Selection execute(QPoint seed, const Layer& layer, const cv::Mat& sourceImage) = 0; + }; +} + +#endif diff --git a/Grinder/cv/algorithms/wand/WatershedWandAlgorithm.cpp b/Grinder/cv/algorithms/wand/WatershedWandAlgorithm.cpp index fbc61e02d9d102a38f7ffd004e1f1f9aa9c47bd4..b68f78ced3fb5362404cb7600acd1e5286d815b5 100644 --- a/Grinder/cv/algorithms/wand/WatershedWandAlgorithm.cpp +++ b/Grinder/cv/algorithms/wand/WatershedWandAlgorithm.cpp @@ -11,9 +11,9 @@ const WandAlgorithm::AlgorithmType WatershedWandAlgorithm::type_value = WandAlgorithm::AlgorithmType::Watershed; -LayerPixelsData::Selection WatershedWandAlgorithm::execute(QPoint seed, const Layer& layer) +LayerPixelsData::Selection WatershedWandAlgorithm::execute(QPoint seed, const Layer& layer, const cv::Mat& sourceImage) { - auto layerSize = layer.getLayerSize(); + QSize layerSize = !sourceImage.empty() ? QSize{sourceImage.cols, sourceImage.rows} : layer.getLayerSize(); // Create markers for the Watershed algorithm cv::Mat markers = cv::Mat::zeros(layerSize.height(), layerSize.width(), CV_32SC1); @@ -21,7 +21,13 @@ LayerPixelsData::Selection WatershedWandAlgorithm::execute(QPoint seed, const La cv::circle(markers, cv::Point{0, 0}, 3, cv::Scalar::all(1), -1); // Extra marker to make Watershed work // Use the Watershed algorithm to extract the clicked region - auto pixelsMatrix = layer.layerPixels().data().toMatrix(); + cv::Mat pixelsMatrix; + + if (!sourceImage.empty()) + pixelsMatrix = sourceImage.clone(); + else + pixelsMatrix = layer.layerPixels().data().toMatrix(); + cv::watershed(pixelsMatrix, markers); // Get the correct marker diff --git a/Grinder/cv/algorithms/wand/WatershedWandAlgorithm.h b/Grinder/cv/algorithms/wand/WatershedWandAlgorithm.h index 775fba8571af9f0649bb34607366bb12baed6fdb..eedb076e623dcff6ebb8b454d60309c1df5e213b 100644 --- a/Grinder/cv/algorithms/wand/WatershedWandAlgorithm.h +++ b/Grinder/cv/algorithms/wand/WatershedWandAlgorithm.h @@ -1,28 +1,28 @@ -/****************************************************************************** - * File: WatershedWandAlgorithm.h - * Date: 08.8.2018 - *****************************************************************************/ - -#ifndef WATERSHEDWANDALGORITHM_H -#define WATERSHEDWANDALGORITHM_H - -#include "WandAlgorithm.h" - -namespace grndr -{ - class WatershedWandAlgorithm : public WandAlgorithm - { - Q_OBJECT - - public: - static const WandAlgorithm::AlgorithmType type_value; - - public: - virtual LayerPixelsData::Selection execute(QPoint seed, const Layer& layer) override; - - protected: - virtual void createProperties() override; - }; -} - -#endif +/****************************************************************************** + * File: WatershedWandAlgorithm.h + * Date: 08.8.2018 + *****************************************************************************/ + +#ifndef WATERSHEDWANDALGORITHM_H +#define WATERSHEDWANDALGORITHM_H + +#include "WandAlgorithm.h" + +namespace grndr +{ + class WatershedWandAlgorithm : public WandAlgorithm + { + Q_OBJECT + + public: + static const WandAlgorithm::AlgorithmType type_value; + + public: + virtual LayerPixelsData::Selection execute(QPoint seed, const Layer& layer, const cv::Mat& sourceImage) override; + + protected: + virtual void createProperties() override; + }; +} + +#endif diff --git a/Grinder/image/ImageBuild.cpp b/Grinder/image/ImageBuild.cpp index 173af86659fb9d98dff31040e7387ea0757ec9a4..401f63541d788c3aadcba887151fd04cd0b991f3 100644 --- a/Grinder/image/ImageBuild.cpp +++ b/Grinder/image/ImageBuild.cpp @@ -153,7 +153,7 @@ void ImageBuild::autoGenerateImageTag(QColor color) } } -cv::Mat ImageBuild::renderImageBuild(std::vector<const Layer*> allowedLayers, bool renderableOnly) const +cv::Mat ImageBuild::renderImageBuild(std::vector<const Layer*> allowedLayers, bool renderableOnly, bool ignoreAlpha) const { // Make sure that the rendered image is an 8-bit color one cv::Mat renderedImage; @@ -168,7 +168,7 @@ cv::Mat ImageBuild::renderImageBuild(std::vector<const Layer*> allowedLayers, bo if (allowedLayers.empty() || std::find(allowedLayers.cbegin(), allowedLayers.cend(), layer.get()) != allowedLayers.cend()) { if (layer->hasFlag(Layer::Flag::Renderable) || !renderableOnly) - layer->renderLayer(renderedImage); + layer->renderLayer(renderedImage, true, ignoreAlpha); } } diff --git a/Grinder/image/ImageBuild.h b/Grinder/image/ImageBuild.h index b7e41dc9d82b551846263bd01d302eaf7f698316..7f28e941483674b9ec073c99ae4e2a63d6043ea0 100644 --- a/Grinder/image/ImageBuild.h +++ b/Grinder/image/ImageBuild.h @@ -48,7 +48,7 @@ namespace grndr void autoGenerateImageTag(QColor color); public: - cv::Mat renderImageBuild(std::vector<const Layer*> allowedLayers = {}, bool renderableOnly = true) const; + cv::Mat renderImageBuild(std::vector<const Layer*> allowedLayers = {}, bool renderableOnly = true, bool ignoreAlpha = true) const; cv::Mat renderImageBuildItems(QColor backgroundColor, std::vector<const Layer*> allowedLayers = {}, bool renderableOnly = true) const; ImageTagsBitmap renderImageTagsBitmap(bool renderableOnly = true) const; diff --git a/Grinder/image/Layer.cpp b/Grinder/image/Layer.cpp index d22f0fc5cc34424270770329a720fcdc357e7f55..80d2af0a0175ca759dd1a680e0722359851ed742 100644 --- a/Grinder/image/Layer.cpp +++ b/Grinder/image/Layer.cpp @@ -94,7 +94,7 @@ void Layer::removeDraftItem(const DraftItem* item) } } -void Layer::renderLayer(cv::Mat& image, bool renderBackground) const +void Layer::renderLayer(cv::Mat& image, bool renderBackground, bool ignoreAlpha) const { LayerPixels renderedPixels = _layerPixels; @@ -120,7 +120,7 @@ void Layer::renderLayer(cv::Mat& image, bool renderBackground) const } // Render the pixels to the target image - renderedPixels.data().renderPixels(image, 1.0f); + renderedPixels.data().renderPixels(image, ignoreAlpha ? 1.0f : static_cast<float>(_alpha) / 100.0f); } void Layer::renderImageTag(const ImageTag* imageTag, ImageTagsBitmap& imageTagsBitmap) const diff --git a/Grinder/image/Layer.h b/Grinder/image/Layer.h index 4db5c8933504a1fb20be9c45b7d8d4dde0fb888f..892bd26eb70b5b4c5f5b070811bd5ebe3df10813 100644 --- a/Grinder/image/Layer.h +++ b/Grinder/image/Layer.h @@ -56,7 +56,7 @@ namespace grndr void removeDraftItem(const DraftItem* item); public: - void renderLayer(cv::Mat& image, bool renderBackground = true) const; + void renderLayer(cv::Mat& image, bool renderBackground = true, bool ignoreAlpha = true) const; void renderImageTag(const ImageTag* imageTag, ImageTagsBitmap& imageTagsBitmap) const; public: diff --git a/Grinder/image/LayerPixelsData.cpp b/Grinder/image/LayerPixelsData.cpp index 8cd690bb3ebeffbad90e85d5d6d56bb2971dfe85..efdc5cef285c0c3a079e4bffb7db9fef01753be4 100644 --- a/Grinder/image/LayerPixelsData.cpp +++ b/Grinder/image/LayerPixelsData.cpp @@ -65,17 +65,40 @@ void LayerPixelsData::fill(const LayerPixelsData::Selection& selection, QColor c emitDataModified(); } -void LayerPixelsData::floodFill(int x, int y, QColor color, float tolerance, bool perceivedDifference) +void LayerPixelsData::floodFill(int x, int y, QColor color, float tolerance, bool perceivedDifference, const cv::Mat& sourceImage) { // Perform the flood fill on a Matrix for simplicity & efficiency + QColor sourceColor = get(x, y); + + if (!sourceImage.empty()) + { + ConstPixelAccessor pixels{sourceImage}; + sourceColor = pixels.at(y, x); + } + cv::Mat matrix = toMatrix(true); - FloodFill floodFill{matrix, QPoint{x, y}, get(x, y), color, tolerance, perceivedDifference}; + FloodFill floodFill{!sourceImage.empty() ? sourceImage : matrix, matrix, QPoint{x, y}, sourceColor, color, tolerance, perceivedDifference}; floodFill.execute(); // Clear the original pixels data and draw the modified image fromMatrix(matrix); } +void LayerPixelsData::copy(const LayerPixelsData& source) +{ + for (int y = 0; y < std::min(_pixels->height(), source._pixels->height()); ++y) + { + auto rgbIn = reinterpret_cast<const QRgb*>(source._pixels->constScanLine(y)); + auto rgbOut = reinterpret_cast<QRgb*>(_pixels->scanLine(y)); + + for (int x = 0; x < std::min(_pixels->width(), source._pixels->width()); ++x) + { + if (rgbIn[x] != 0) + rgbOut[x] = rgbIn[x]; + } + } +} + void LayerPixelsData::clear(int x, int y) { if (checkBounds(x, y)) @@ -99,7 +122,6 @@ void LayerPixelsData::resize(QSize newSize) createPixels(newSize); emitDataModified(); } - } } diff --git a/Grinder/image/LayerPixelsData.h b/Grinder/image/LayerPixelsData.h index 6eac07c9a48b2b1c4bbfe7ffabd47df4ae66714a..625ca62eacafbc27a6f977cb5dcf6afc0a6c74a5 100644 --- a/Grinder/image/LayerPixelsData.h +++ b/Grinder/image/LayerPixelsData.h @@ -44,8 +44,9 @@ namespace grndr void set(int x, int y, QColor color); void set(QPoint pos, QColor color) { set(pos.x(), pos.y(), color); } void fill(const Selection& selection, QColor color); - void floodFill(int x, int y, QColor color, float tolerance, bool perceivedDifference); - void floodFill(QPoint seedPos, QColor color, float tolerance, bool perceivedDifference) { floodFill(seedPos.x(), seedPos.y(), color, tolerance, perceivedDifference); } + void floodFill(int x, int y, QColor color, float tolerance, bool perceivedDifference, const cv::Mat& sourceImage = {}); + void floodFill(QPoint seedPos, QColor color, float tolerance, bool perceivedDifference, const cv::Mat& inputImage = {}) { floodFill(seedPos.x(), seedPos.y(), color, tolerance, perceivedDifference, inputImage); } + void copy(const LayerPixelsData& source); void clear(int x, int y); void clear(QPoint pos) { clear(pos.x(), pos.y()); } void clear(const Selection& selection) { fill(selection, QColor{}); } diff --git a/Grinder/res/Grinder.qrc b/Grinder/res/Grinder.qrc index 2be16db5fa5bb7259338ec5df96b6280ce1f8fae..e5b1be04d4ecf50c16d128b14cf4a5c83d3ffd9d 100644 --- a/Grinder/res/Grinder.qrc +++ b/Grinder/res/Grinder.qrc @@ -76,6 +76,7 @@ <file>icons/overlay-tagged.png</file> <file>icons/batch.png</file> <file>icons/folder-out-interface-symbol.png</file> + <file>icons/sampling-image.png</file> </qresource> <qresource prefix="/"> <file>css/global.css</file> diff --git a/Grinder/res/Resources.h b/Grinder/res/Resources.h index c1f1539e9b9f63007cc0588dbba6cfad6943ac19..d6e7f6d9a8bf455c77fb354843d0a2dbb08f3799 100644 --- a/Grinder/res/Resources.h +++ b/Grinder/res/Resources.h @@ -88,6 +88,7 @@ #define FILE_ICON_EDITOR_LASSO ":/icons/icons/lasso.png" #define FILE_ICON_EDITOR_GRABCUT ":/icons/icons/painting/scissors.png" #define FILE_ICON_EDITOR_MORPH ":/icons/icons/arrows-group-interface-symbol-to-expand.png" +#define FILE_ICON_EDITOR_SAMPLING_IMAGE ":/icons/icons/sampling-image.png" /* Cursors */ diff --git a/Grinder/res/icons/sampling-image.png b/Grinder/res/icons/sampling-image.png new file mode 100644 index 0000000000000000000000000000000000000000..b42bac8735d1d09fabc9ada69323c4c1c3080899 Binary files /dev/null and b/Grinder/res/icons/sampling-image.png differ diff --git a/Grinder/ui/image/ImageEditor.cpp b/Grinder/ui/image/ImageEditor.cpp index 53cbac622972b061de6fa2a02b333e90acba375c..a64801b97f27605aa2a0bc92fc9d367085bcb35f 100644 --- a/Grinder/ui/image/ImageEditor.cpp +++ b/Grinder/ui/image/ImageEditor.cpp @@ -1,66 +1,86 @@ -/****************************************************************************** - * File: ImageEditor.cpp - * Date: 27.3.2018 - *****************************************************************************/ - -#include "Grinder.h" -#include "ImageEditor.h" -#include "core/GrinderApplication.h" - -const char* ImageEditor::Serialization_Value_Block = "Block"; - -ImageEditor::ImageEditor() : - _editorController{this}, _editorEnvironment{&_editorController}, _editorTools{this}, _editorDockWidget{new ImageEditorDockWidget{}}, _editorWidget{new ImageEditorWidget{this, _editorDockWidget}} -{ - // Assign the editor widget to the dock widget - _editorDockWidget->setWidget(_editorWidget); -} - -void ImageEditor::setImageBuild(const std::shared_ptr<ImageBuild>& imageBuild) -{ - _editorWidget->setImageBuild(imageBuild); - _editorDockWidget->updateDockName(imageBuild->block()); - _editorEnvironment.selection().setImageBuild(imageBuild); -} - -void ImageEditor::serialize(SerializationContext& ctx) const -{ - // Serialize environment - ctx.beginGroup(ImageEditorEnvironment::Serialization_Group); - _editorEnvironment.serialize(ctx); - ctx.endGroup(); - - // Serialize tools - ctx.beginGroup(ImageEditorToolList::Serialization_Group, true); - _editorTools.serialize(ctx); - ctx.endGroup(); - - // Serialize editor widget - ctx.beginGroup(ImageEditorWidget::Serialization_Group); - _editorWidget->serialize(ctx); - ctx.endGroup(); -} - -void ImageEditor::deserialize(DeserializationContext& ctx) -{ - // Deserialize environment - if (ctx.beginGroup(ImageEditorEnvironment::Serialization_Group)) - { - _editorEnvironment.deserialize(ctx); - ctx.endGroup(); - } - - // Deserialize tools - if (ctx.beginGroup(ImageEditorToolList::Serialization_Group)) - { - _editorTools.deserialize(ctx); - ctx.endGroup(); - } - - // Deserialize editor widget - if (ctx.beginGroup(ImageEditorWidget::Serialization_Group)) - { - _editorWidget->deserialize(ctx); - ctx.endGroup(); - } -} +/****************************************************************************** + * File: ImageEditor.cpp + * Date: 27.3.2018 + *****************************************************************************/ + +#include "Grinder.h" +#include "ImageEditor.h" +#include "core/GrinderApplication.h" + +const char* ImageEditor::Serialization_Value_Block = "Block"; + +ImageEditor::ImageEditor() : + _editorController{this}, _editorEnvironment{&_editorController}, _editorTools{this}, _editorDockWidget{new ImageEditorDockWidget{}}, _editorWidget{new ImageEditorWidget{this, _editorDockWidget}} +{ + // Assign the editor widget to the dock widget + _editorDockWidget->setWidget(_editorWidget); +} + +void ImageEditor::setImageBuild(const std::shared_ptr<ImageBuild>& imageBuild) +{ + _editorWidget->setImageBuild(imageBuild); + _editorDockWidget->updateDockName(imageBuild->block()); + _editorEnvironment.selection().setImageBuild(imageBuild); +} + +cv::Mat ImageEditor::getDisplayedImage(bool ignoreLayersAlpha) +{ + if (auto imageBuild = _editorController.activeImageBuild()) + { + // Only include currently displayed layers + std::vector<const Layer*> visibleLayers; + + for (auto layer : imageBuild->layers()) + { + if (layer->isVisible()) + visibleLayers.push_back(layer.get()); + } + + // Render the current image as it is currently displayed to the user + return imageBuild->renderImageBuild(visibleLayers, false, ignoreLayersAlpha); + } + else + return {}; +} + +void ImageEditor::serialize(SerializationContext& ctx) const +{ + // Serialize environment + ctx.beginGroup(ImageEditorEnvironment::Serialization_Group); + _editorEnvironment.serialize(ctx); + ctx.endGroup(); + + // Serialize tools + ctx.beginGroup(ImageEditorToolList::Serialization_Group, true); + _editorTools.serialize(ctx); + ctx.endGroup(); + + // Serialize editor widget + ctx.beginGroup(ImageEditorWidget::Serialization_Group); + _editorWidget->serialize(ctx); + ctx.endGroup(); +} + +void ImageEditor::deserialize(DeserializationContext& ctx) +{ + // Deserialize environment + if (ctx.beginGroup(ImageEditorEnvironment::Serialization_Group)) + { + _editorEnvironment.deserialize(ctx); + ctx.endGroup(); + } + + // Deserialize tools + if (ctx.beginGroup(ImageEditorToolList::Serialization_Group)) + { + _editorTools.deserialize(ctx); + ctx.endGroup(); + } + + // Deserialize editor widget + if (ctx.beginGroup(ImageEditorWidget::Serialization_Group)) + { + _editorWidget->deserialize(ctx); + ctx.endGroup(); + } +} diff --git a/Grinder/ui/image/ImageEditor.h b/Grinder/ui/image/ImageEditor.h index 614d46a0badbd380be42c50e7e0df503a3fdfe8d..c50a3b4a3cb215f38008d57d0c927322febfb6c3 100644 --- a/Grinder/ui/image/ImageEditor.h +++ b/Grinder/ui/image/ImageEditor.h @@ -1,59 +1,62 @@ -/****************************************************************************** - * File: ImageEditor.h - * Date: 27.3.2018 - *****************************************************************************/ - -#ifndef IMAGEEDITOR_H -#define IMAGEEDITOR_H - -#include "controller/ImageEditorController.h" -#include "ImageEditorEnvironment.h" -#include "ImageEditorDockWidget.h" -#include "ImageEditorWidget.h" -#include "ImageEditorToolList.h" - -namespace grndr -{ - class Block; - - class ImageEditor : public QObject - { - Q_OBJECT - - public: - static const char* Serialization_Value_Block; - - public: - ImageEditor(); - - public: - void setImageBuild(const std::shared_ptr<ImageBuild>& imageBuild); - - public: - ImageEditorController& controller() { return _editorController; } - const ImageEditorController& controller() const { return _editorController; } - ImageEditorEnvironment& environment() { return _editorEnvironment; } - const ImageEditorEnvironment& environment() const { return _editorEnvironment; } - ImageEditorToolList& editorTools() { return _editorTools; } - const ImageEditorToolList& editorTools() const { return _editorTools; } - - ImageEditorDockWidget* dockWidget() { return _editorDockWidget; } - const ImageEditorDockWidget* dockWidget() const { return _editorDockWidget; } - ImageEditorWidget* editorWidget() { return _editorWidget; } - const ImageEditorWidget* editorWidget() const { return _editorWidget; } - - public: - void serialize(SerializationContext& ctx) const; - void deserialize(DeserializationContext& ctx); - - private: - ImageEditorController _editorController; - ImageEditorEnvironment _editorEnvironment; - ImageEditorToolList _editorTools; - - ImageEditorDockWidget* _editorDockWidget{nullptr}; - ImageEditorWidget* _editorWidget{nullptr}; - }; -} - -#endif +/****************************************************************************** + * File: ImageEditor.h + * Date: 27.3.2018 + *****************************************************************************/ + +#ifndef IMAGEEDITOR_H +#define IMAGEEDITOR_H + +#include "controller/ImageEditorController.h" +#include "ImageEditorEnvironment.h" +#include "ImageEditorDockWidget.h" +#include "ImageEditorWidget.h" +#include "ImageEditorToolList.h" + +namespace grndr +{ + class Block; + + class ImageEditor : public QObject + { + Q_OBJECT + + public: + static const char* Serialization_Value_Block; + + public: + ImageEditor(); + + public: + void setImageBuild(const std::shared_ptr<ImageBuild>& imageBuild); + + public: + cv::Mat getDisplayedImage(bool ignoreLayersAlpha = true); + + public: + ImageEditorController& controller() { return _editorController; } + const ImageEditorController& controller() const { return _editorController; } + ImageEditorEnvironment& environment() { return _editorEnvironment; } + const ImageEditorEnvironment& environment() const { return _editorEnvironment; } + ImageEditorToolList& editorTools() { return _editorTools; } + const ImageEditorToolList& editorTools() const { return _editorTools; } + + ImageEditorDockWidget* dockWidget() { return _editorDockWidget; } + const ImageEditorDockWidget* dockWidget() const { return _editorDockWidget; } + ImageEditorWidget* editorWidget() { return _editorWidget; } + const ImageEditorWidget* editorWidget() const { return _editorWidget; } + + public: + void serialize(SerializationContext& ctx) const; + void deserialize(DeserializationContext& ctx); + + private: + ImageEditorController _editorController; + ImageEditorEnvironment _editorEnvironment; + ImageEditorToolList _editorTools; + + ImageEditorDockWidget* _editorDockWidget{nullptr}; + ImageEditorWidget* _editorWidget{nullptr}; + }; +} + +#endif diff --git a/Grinder/ui/image/ImageEditorEnvironment.cpp b/Grinder/ui/image/ImageEditorEnvironment.cpp index e55b56d6f0caddf97a5054ced7b3ffa6f54947b7..1d8ebb0bea683a0c37b0ba0ed872e7fadb60dd24 100644 --- a/Grinder/ui/image/ImageEditorEnvironment.cpp +++ b/Grinder/ui/image/ImageEditorEnvironment.cpp @@ -1,76 +1,88 @@ -/****************************************************************************** - * File: ImageEditorEnvironment.cpp - * Date: 26.3.2018 - *****************************************************************************/ - -#include "Grinder.h" -#include "ImageEditorEnvironment.h" -#include "controller/ImageEditorController.h" - -const char* ImageEditorEnvironment::Serialization_Group = "Environment"; - -const char* ImageEditorEnvironment::Serialization_Value_PrimaryColor = "PrimaryColor"; -const char* ImageEditorEnvironment::Serialization_Value_ShowDirectionArrows = "ShowDirectionArrows"; -const char* ImageEditorEnvironment::Serialization_Value_ShowTags = "ShowTags"; -const char* ImageEditorEnvironment::Serialization_Value_SnapToEdges = "SnapToEdges"; - -ImageEditorEnvironment::ImageEditorEnvironment(ImageEditorController* controller) : - _editorController{controller}, _selection{controller->imageEditor()} -{ - if (!controller) - throw std::invalid_argument{_EXCPT("controller may not be null")}; -} - -void ImageEditorEnvironment::setPrimaryColor(QColor color) -{ - if (color != _primaryColor) - { - _primaryColor = color; - emit primaryColorChanged(color); - } -} - -void ImageEditorEnvironment::setShowDirectionArrows(bool show) -{ - if (show != _showDirectionArrows) - { - _showDirectionArrows = show; - emit showDirectionArrowsChanged(show); - } -} - -void ImageEditorEnvironment::setShowTags(bool show) -{ - if (show != _showTags) - { - _showTags = show; - emit showTagsChanged(show); - } -} - -void ImageEditorEnvironment::setSnapToEdges(bool snap) -{ - if (snap != _snapToEdges) - { - _snapToEdges = snap; - emit snapToEdgesChanged(snap); - } -} - -void ImageEditorEnvironment::serialize(SerializationContext& ctx) const -{ - // Serialize values - ctx.settings()(Serialization_Value_PrimaryColor) = _primaryColor.name(); - ctx.settings()(Serialization_Value_ShowDirectionArrows) = _showDirectionArrows; - ctx.settings()(Serialization_Value_ShowTags) = _showTags; - ctx.settings()(Serialization_Value_SnapToEdges) = _snapToEdges; -} - -void ImageEditorEnvironment::deserialize(DeserializationContext& ctx) -{ - // Deserialize values - setPrimaryColor(QColor{ctx.settings()(Serialization_Value_PrimaryColor).toString()}); - setShowDirectionArrows(ctx.settings()(Serialization_Value_ShowDirectionArrows, true).toBool()); - setShowTags(ctx.settings()(Serialization_Value_ShowTags, true).toBool()); - setSnapToEdges(ctx.settings()(Serialization_Value_SnapToEdges, true).toBool()); -} +/****************************************************************************** + * File: ImageEditorEnvironment.cpp + * Date: 26.3.2018 + *****************************************************************************/ + +#include "Grinder.h" +#include "ImageEditorEnvironment.h" +#include "controller/ImageEditorController.h" + +const char* ImageEditorEnvironment::Serialization_Group = "Environment"; + +const char* ImageEditorEnvironment::Serialization_Value_PrimaryColor = "PrimaryColor"; +const char* ImageEditorEnvironment::Serialization_Value_ShowDirectionArrows = "ShowDirectionArrows"; +const char* ImageEditorEnvironment::Serialization_Value_ShowTags = "ShowTags"; +const char* ImageEditorEnvironment::Serialization_Value_SnapToEdges = "SnapToEdges"; +const char* ImageEditorEnvironment::Serialization_Value_SamplingMode = "SamplingMode"; + +ImageEditorEnvironment::ImageEditorEnvironment(ImageEditorController* controller) : + _editorController{controller}, _selection{controller->imageEditor()} +{ + if (!controller) + throw std::invalid_argument{_EXCPT("controller may not be null")}; +} + +void ImageEditorEnvironment::setPrimaryColor(QColor color) +{ + if (color != _primaryColor) + { + _primaryColor = color; + emit primaryColorChanged(color); + } +} + +void ImageEditorEnvironment::setShowDirectionArrows(bool show) +{ + if (show != _showDirectionArrows) + { + _showDirectionArrows = show; + emit showDirectionArrowsChanged(show); + } +} + +void ImageEditorEnvironment::setShowTags(bool show) +{ + if (show != _showTags) + { + _showTags = show; + emit showTagsChanged(show); + } +} + +void ImageEditorEnvironment::setSnapToEdges(bool snap) +{ + if (snap != _snapToEdges) + { + _snapToEdges = snap; + emit snapToEdgesChanged(snap); + } +} + +void ImageEditorEnvironment::setSamplingMode(SamplingMode mode) +{ + if (mode != _samplingMode) + { + _samplingMode = mode; + emit samplingModeChanged(mode); + } +} + +void ImageEditorEnvironment::serialize(SerializationContext& ctx) const +{ + // Serialize values + ctx.settings()(Serialization_Value_PrimaryColor) = _primaryColor.name(); + ctx.settings()(Serialization_Value_ShowDirectionArrows) = _showDirectionArrows; + ctx.settings()(Serialization_Value_ShowTags) = _showTags; + ctx.settings()(Serialization_Value_SnapToEdges) = _snapToEdges; + ctx.settings()(Serialization_Value_SamplingMode) = static_cast<int>(_samplingMode); +} + +void ImageEditorEnvironment::deserialize(DeserializationContext& ctx) +{ + // Deserialize values + setPrimaryColor(QColor{ctx.settings()(Serialization_Value_PrimaryColor).toString()}); + setShowDirectionArrows(ctx.settings()(Serialization_Value_ShowDirectionArrows, true).toBool()); + setShowTags(ctx.settings()(Serialization_Value_ShowTags, true).toBool()); + setSnapToEdges(ctx.settings()(Serialization_Value_SnapToEdges, true).toBool()); + setSamplingMode(static_cast<SamplingMode>(ctx.settings()(Serialization_Value_SamplingMode, static_cast<int>(SamplingMode::Layer)).toInt())); +} diff --git a/Grinder/ui/image/ImageEditorEnvironment.h b/Grinder/ui/image/ImageEditorEnvironment.h index 478f5d70e4cb784276adfeac8ae38f47c96a0597..f89e9d499cbeb17b0f71fda727e83ebe8f9ccc41 100644 --- a/Grinder/ui/image/ImageEditorEnvironment.h +++ b/Grinder/ui/image/ImageEditorEnvironment.h @@ -1,72 +1,86 @@ -/****************************************************************************** - * File: ImageEditorEnvironment.h - * Date: 26.3.2018 - *****************************************************************************/ - -#ifndef IMAGEEDITORENVIRONMENT_H -#define IMAGEEDITORENVIRONMENT_H - -#include <QColor> - -#include "ImageEditorSelection.h" -#include "common/serialization/SerializationContext.h" -#include "common/serialization/DeserializationContext.h" - -namespace grndr -{ - class ImageEditorController; - - class ImageEditorEnvironment : public QObject - { - Q_OBJECT - - public: - static const char* Serialization_Group; - - static const char* Serialization_Value_PrimaryColor; - static const char* Serialization_Value_ShowDirectionArrows; - static const char* Serialization_Value_ShowTags; - static const char* Serialization_Value_SnapToEdges; - - public: - ImageEditorEnvironment(ImageEditorController* controller); - - public: - ImageEditorSelection& selection() { return _selection; } - const ImageEditorSelection& selection() const { return _selection; } - - QColor getPrimaryColor() const { return _primaryColor; } - void setPrimaryColor(QColor color); - - bool showDirectionArrows() const { return _showDirectionArrows; } - void setShowDirectionArrows(bool show); - bool showTags() const { return _showTags; } - void setShowTags(bool show); - bool snapToEdges() const { return _snapToEdges; } - void setSnapToEdges(bool snap); - - public: - void serialize(SerializationContext& ctx) const; - void deserialize(DeserializationContext& ctx); - - signals: - void primaryColorChanged(QColor); - void showDirectionArrowsChanged(bool); - void showTagsChanged(bool); - void snapToEdgesChanged(bool); - - private: - ImageEditorController* _editorController{nullptr}; - - private: - ImageEditorSelection _selection; - - QColor _primaryColor{255, 255, 255}; - - bool _showDirectionArrows{true}; - bool _showTags{true}; - bool _snapToEdges{true}; - }; -} - -#endif +/****************************************************************************** + * File: ImageEditorEnvironment.h + * Date: 26.3.2018 + *****************************************************************************/ + +#ifndef IMAGEEDITORENVIRONMENT_H +#define IMAGEEDITORENVIRONMENT_H + +#include <QColor> + +#include "ImageEditorSelection.h" +#include "common/serialization/SerializationContext.h" +#include "common/serialization/DeserializationContext.h" + +namespace grndr +{ + class ImageEditorController; + + class ImageEditorEnvironment : public QObject + { + Q_OBJECT + + public: + static const char* Serialization_Group; + + static const char* Serialization_Value_PrimaryColor; + static const char* Serialization_Value_ShowDirectionArrows; + static const char* Serialization_Value_ShowTags; + static const char* Serialization_Value_SnapToEdges; + static const char* Serialization_Value_SamplingMode; + + public: + enum class SamplingMode + { + Image, + Layer, + }; + + public: + ImageEditorEnvironment(ImageEditorController* controller); + + public: + ImageEditorSelection& selection() { return _selection; } + const ImageEditorSelection& selection() const { return _selection; } + + QColor getPrimaryColor() const { return _primaryColor; } + void setPrimaryColor(QColor color); + + bool showDirectionArrows() const { return _showDirectionArrows; } + void setShowDirectionArrows(bool show); + bool showTags() const { return _showTags; } + void setShowTags(bool show); + bool snapToEdges() const { return _snapToEdges; } + void setSnapToEdges(bool snap); + + SamplingMode getSamplingMode() const { return _samplingMode; } + void setSamplingMode(SamplingMode mode); + + public: + void serialize(SerializationContext& ctx) const; + void deserialize(DeserializationContext& ctx); + + signals: + void primaryColorChanged(QColor); + void showDirectionArrowsChanged(bool); + void showTagsChanged(bool); + void snapToEdgesChanged(bool); + void samplingModeChanged(SamplingMode); + + private: + ImageEditorController* _editorController{nullptr}; + + private: + ImageEditorSelection _selection; + + QColor _primaryColor{255, 255, 255}; + + bool _showDirectionArrows{true}; + bool _showTags{true}; + bool _snapToEdges{true}; + + SamplingMode _samplingMode{SamplingMode::Layer}; + }; +} + +#endif diff --git a/Grinder/ui/image/ImageEditorSelection.cpp b/Grinder/ui/image/ImageEditorSelection.cpp index 9add74d17cfc98bf0b188d6246f71af69a74da2f..106a18babce37de2341fb6dd04e4d19a35bb4c25 100644 --- a/Grinder/ui/image/ImageEditorSelection.cpp +++ b/Grinder/ui/image/ImageEditorSelection.cpp @@ -95,7 +95,14 @@ void ImageEditorSelection::grabCutSelection(int iterations) { if (auto layer = _imageEditor->controller().activeLayer()) { - auto layerSize = layer->getLayerSize(); + cv::Mat pixelsMatrix; + + if (_imageEditor->environment().getSamplingMode() == ImageEditorEnvironment::SamplingMode::Image) + pixelsMatrix = _imageEditor->getDisplayedImage(); + else + pixelsMatrix = layer->layerPixels().data().toMatrix(); + + auto layerSize = QSize{pixelsMatrix.cols, pixelsMatrix.rows}; cv::Mat mask{layerSize.height(), layerSize.width(), CV_8UC1, cv::Scalar::all(cv::GC_BGD)}; for (int x = 0; x < mask.cols; ++x) @@ -108,7 +115,6 @@ void ImageEditorSelection::grabCutSelection(int iterations) } // Run the GrabCut algorithm - auto pixelsMatrix = layer->layerPixels().data().toMatrix(); cv::Mat backgroundModel = cv::Mat::zeros(1, 65, CV_64FC1); cv::Mat foregroundModel = cv::Mat::zeros(1, 65, CV_64FC1); diff --git a/Grinder/ui/image/ImageEditorTool.cpp b/Grinder/ui/image/ImageEditorTool.cpp index 31ca6a1e233beab8a371cc965829fb83356af0f8..6dd889f8d0b611d89329f72b7740e744df2e1d81 100644 --- a/Grinder/ui/image/ImageEditorTool.cpp +++ b/Grinder/ui/image/ImageEditorTool.cpp @@ -1,87 +1,87 @@ -/****************************************************************************** - * File: ImageEditorTool.cpp - * Date: 22.3.2018 - *****************************************************************************/ - -#include "Grinder.h" -#include "ImageEditorTool.h" -#include "ImageEditor.h" -#include "ui/visscene/RubberBandNode.h" - -const char* ImageEditorTool::Serialization_Value_Type = "Type"; - -ImageEditorTool::ImageEditorTool(ImageEditor* imageEditor, QString name, QString icon, QString shortcut, QCursor cursor) : ImageEditorComponent(imageEditor), - _name{name}, _icon{icon}, _shortcut{shortcut}, _cursor{cursor} -{ - if (!imageEditor) - throw std::invalid_argument{_EXCPT("imageEditor may not be null")}; -} - -void ImageEditorTool::initImageEditorTool() -{ - createProperties(); - updateProperties(); -} - -void ImageEditorTool::toolActivated(ImageEditorTool* prevTool) -{ - Q_UNUSED(prevTool); - - _isActive = true; - - if (_supportRightMouseButton && _imageEditor->controller().activeScene()) - { - // If the tool supports the right mouse button, we need to disable the view's context menu - _prevContextMenuPolicy = _imageEditor->controller().activeScene()->view()->contextMenuPolicy(); - _imageEditor->controller().activeScene()->view()->setContextMenuPolicy(Qt::NoContextMenu); - } -} - -void ImageEditorTool::toolDeactivated(ImageEditorTool* nextTool) -{ - Q_UNUSED(nextTool); - - _isActive = false; - - if (_supportRightMouseButton && _imageEditor->controller().activeScene()) - _imageEditor->controller().activeScene()->view()->setContextMenuPolicy(_prevContextMenuPolicy); - - removeRubberBand(); -} - -void ImageEditorTool::serialize(SerializationContext& ctx) const -{ - PropertyObject::serialize(ctx); - - // Serialize values - ctx.settings()(Serialization_Value_Type) = getToolType(); -} - -void ImageEditorTool::deserialize(DeserializationContext& ctx) -{ - PropertyObject::deserialize(ctx); -} - -void ImageEditorTool::createRubberBand(QPointF pos) -{ - if (!_rubberBandNode) - { - if (auto scene = _imageEditor->controller().activeScene()) - _rubberBandNode = new RubberBandNode{scene, pos}; - } -} - -void ImageEditorTool::removeRubberBand() -{ - if (_rubberBandNode) - { - delete _rubberBandNode; - _rubberBandNode = nullptr; - - if (auto scene = _imageEditor->controller().activeScene()) - { - scene->view()->setDragMode(QGraphicsView::ScrollHandDrag); - scene->view()->setDragMode(QGraphicsView::RubberBandDrag); - } - } -} +/****************************************************************************** + * File: ImageEditorTool.cpp + * Date: 22.3.2018 + *****************************************************************************/ + +#include "Grinder.h" +#include "ImageEditorTool.h" +#include "ImageEditor.h" +#include "ui/visscene/RubberBandNode.h" + +const char* ImageEditorTool::Serialization_Value_Type = "Type"; + +ImageEditorTool::ImageEditorTool(ImageEditor* imageEditor, QString name, QString icon, QString shortcut, QCursor cursor) : ImageEditorComponent(imageEditor), + _name{name}, _icon{icon}, _shortcut{shortcut}, _cursor{cursor} +{ + if (!imageEditor) + throw std::invalid_argument{_EXCPT("imageEditor may not be null")}; +} + +void ImageEditorTool::initImageEditorTool() +{ + createProperties(); + updateProperties(); +} + +void ImageEditorTool::toolActivated(ImageEditorTool* prevTool) +{ + Q_UNUSED(prevTool); + + _isActive = true; + + if (_supportRightMouseButton && _imageEditor->controller().activeScene()) + { + // If the tool supports the right mouse button, we need to disable the view's context menu + _prevContextMenuPolicy = _imageEditor->controller().activeScene()->view()->contextMenuPolicy(); + _imageEditor->controller().activeScene()->view()->setContextMenuPolicy(Qt::NoContextMenu); + } +} + +void ImageEditorTool::toolDeactivated(ImageEditorTool* nextTool) +{ + Q_UNUSED(nextTool); + + _isActive = false; + + if (_supportRightMouseButton && _imageEditor->controller().activeScene()) + _imageEditor->controller().activeScene()->view()->setContextMenuPolicy(_prevContextMenuPolicy); + + removeRubberBand(); +} + +void ImageEditorTool::serialize(SerializationContext& ctx) const +{ + PropertyObject::serialize(ctx); + + // Serialize values + ctx.settings()(Serialization_Value_Type) = getToolType(); +} + +void ImageEditorTool::deserialize(DeserializationContext& ctx) +{ + PropertyObject::deserialize(ctx); +} + +void ImageEditorTool::createRubberBand(QPointF pos) +{ + if (!_rubberBandNode) + { + if (auto scene = _imageEditor->controller().activeScene()) + _rubberBandNode = new RubberBandNode{scene, pos}; + } +} + +void ImageEditorTool::removeRubberBand() +{ + if (_rubberBandNode) + { + delete _rubberBandNode; + _rubberBandNode = nullptr; + + if (auto scene = _imageEditor->controller().activeScene()) + { + scene->view()->setDragMode(QGraphicsView::ScrollHandDrag); + scene->view()->setDragMode(QGraphicsView::RubberBandDrag); + } + } +} diff --git a/Grinder/ui/image/ImageEditorTool.h b/Grinder/ui/image/ImageEditorTool.h index 3c9747c40fed6ca55fe1a2f33f0a8e5bee6e05a9..f9b0b6da0914587315355268f7da78bdd2b1cdb3 100644 --- a/Grinder/ui/image/ImageEditorTool.h +++ b/Grinder/ui/image/ImageEditorTool.h @@ -1,84 +1,84 @@ -/****************************************************************************** - * File: ImageEditorTool.h - * Date: 22.3.2018 - *****************************************************************************/ - -#ifndef IMAGEEDITORTOOL_H -#define IMAGEEDITORTOOL_H - -#include <QIcon> -#include <QCursor> -#include <QKeySequence> - -#include "common/properties/PropertyObject.h" -#include "ui/visscene/VisualSceneInputHandler.h" -#include "ImageEditorComponent.h" - -namespace grndr -{ - class ImageEditor; - class RubberBandNode; - - class ImageEditorTool : public PropertyObject, public ImageEditorComponent, public VisualSceneInputHandler - { - Q_OBJECT - - public: - static const char* Serialization_Value_Type; - - public: - ImageEditorTool(ImageEditor* imageEditor, QString name, QString icon, QString shortcut, QCursor cursor = QCursor{Qt::ArrowCursor}); - - public: - void initImageEditorTool(); - - public: - virtual void toolActivated(ImageEditorTool* prevTool); - virtual void toolDeactivated(ImageEditorTool* nextTool); - - public: - virtual void serialize(SerializationContext& ctx) const override; - virtual void deserialize(DeserializationContext& ctx) override; - - public: - virtual QString getToolType() const = 0; - - QString getName() const { return _name; } - QIcon getIcon() const { return _icon; } - QString getShortcut() const { return _shortcut; } - bool isActionShortcut() const { return _isActionShortcut; } - QCursor getCursor() const { return _cursor; } - - bool isActive() const { return _isActive; } - - virtual PropertyObject* getExternalProperties() const { return nullptr; } - - signals: - void changeCursor(const QCursor&); - - void externalPropertiesInvalidated(const PropertyObject*); - - protected: - void createRubberBand(QPointF pos); - void removeRubberBand(); - - protected: - QString _name{""}; - QIcon _icon; - QString _shortcut; - bool _isActionShortcut{true}; - QCursor _cursor; - - bool _supportRightMouseButton{false}; - - bool _isActive{false}; - - protected: - RubberBandNode* _rubberBandNode{nullptr}; - - private: - Qt::ContextMenuPolicy _prevContextMenuPolicy; - }; -} - -#endif +/****************************************************************************** + * File: ImageEditorTool.h + * Date: 22.3.2018 + *****************************************************************************/ + +#ifndef IMAGEEDITORTOOL_H +#define IMAGEEDITORTOOL_H + +#include <QIcon> +#include <QCursor> +#include <QKeySequence> + +#include "common/properties/PropertyObject.h" +#include "ui/visscene/VisualSceneInputHandler.h" +#include "ImageEditorComponent.h" + +namespace grndr +{ + class ImageEditor; + class RubberBandNode; + + class ImageEditorTool : public PropertyObject, public ImageEditorComponent, public VisualSceneInputHandler + { + Q_OBJECT + + public: + static const char* Serialization_Value_Type; + + public: + ImageEditorTool(ImageEditor* imageEditor, QString name, QString icon, QString shortcut, QCursor cursor = QCursor{Qt::ArrowCursor}); + + public: + void initImageEditorTool(); + + public: + virtual void toolActivated(ImageEditorTool* prevTool); + virtual void toolDeactivated(ImageEditorTool* nextTool); + + public: + virtual void serialize(SerializationContext& ctx) const override; + virtual void deserialize(DeserializationContext& ctx) override; + + public: + virtual QString getToolType() const = 0; + + QString getName() const { return _name; } + QIcon getIcon() const { return _icon; } + QString getShortcut() const { return _shortcut; } + bool isActionShortcut() const { return _isActionShortcut; } + QCursor getCursor() const { return _cursor; } + + bool isActive() const { return _isActive; } + + virtual PropertyObject* getExternalProperties() const { return nullptr; } + + signals: + void changeCursor(const QCursor&); + + void externalPropertiesInvalidated(const PropertyObject*); + + protected: + void createRubberBand(QPointF pos); + void removeRubberBand(); + + protected: + QString _name{""}; + QIcon _icon; + QString _shortcut; + bool _isActionShortcut{true}; + QCursor _cursor; + + bool _supportRightMouseButton{false}; + + bool _isActive{false}; + + protected: + RubberBandNode* _rubberBandNode{nullptr}; + + private: + Qt::ContextMenuPolicy _prevContextMenuPolicy; + }; +} + +#endif diff --git a/Grinder/ui/image/ImageEditorToolList.cpp b/Grinder/ui/image/ImageEditorToolList.cpp index 7336363c29f0da9636b8835baa7171a74d18914e..bb459c9b855e133f6a16dc8a08c600dccbd3d655 100644 --- a/Grinder/ui/image/ImageEditorToolList.cpp +++ b/Grinder/ui/image/ImageEditorToolList.cpp @@ -32,6 +32,7 @@ ImageEditorToolList::ImageEditorToolList(ImageEditor* imageEditor) : ImageEditor _tools.reserve(30); // Reserve enough space so that no resizing occurs when adding all tools (can be problematic under Linux) _tools.push_back(ToolEntry{createTool<DefaultImageEditorTool>(imageEditor), nullptr}); _tools.push_back(ToolEntry{createTool<ColorPickerTool>(imageEditor), nullptr}); + _tools.push_back(ToolEntry{}); _tools.push_back(ToolEntry{createTool<BoxDraftItemTool>(imageEditor), nullptr}); _tools.push_back(ToolEntry{createTool<EllipseDraftItemTool>(imageEditor), nullptr}); _tools.push_back(ToolEntry{createTool<LineDraftItemTool>(imageEditor), nullptr}); diff --git a/Grinder/ui/image/ImageEditorView.cpp b/Grinder/ui/image/ImageEditorView.cpp index 2f0b2cdfd95d8d447bf22dc7354680e0e29e0c9c..7fc3b10c049db7fd4e022644ab944b0f96f609c8 100644 --- a/Grinder/ui/image/ImageEditorView.cpp +++ b/Grinder/ui/image/ImageEditorView.cpp @@ -1,334 +1,418 @@ -/****************************************************************************** - * File: ImageEditorView.cpp - * Date: 15.3.2018 - *****************************************************************************/ - -#include "Grinder.h" -#include "ImageEditorView.h" -#include "ImageEditorScene.h" -#include "ImageEditor.h" -#include "DraftItemNode.h" -#include "core/GrinderApplication.h" -#include "controller/ImageEditorController.h" -#include "ui/UIUtils.h" -#include "res/Resources.h" - -ImageEditorView::ImageEditorView(QWidget* parent) : VisualSceneView(parent) -{ - // Set the background of the view to a checkerboard pattern - setBackgroundBrush(QPixmap{FILE_ICON_EDITOR_BACKGROUND}); - - // Set widget border - setStyleSheet(QString{"QGraphicsView { border: 1px solid %1; }"}.arg(QPalette{}.color(QPalette::Dark).name())); - - // Create view actions - _showDirectionsAction = UIUtils::createAction(this, "&Show direction arrows", FILE_ICON_EDITOR_SHOWDIRECTIONS, SLOT(showDirectionArrows()), "Show direction arrows on items"); - _showDirectionsAction->setCheckable(true); - _showDirectionsAction->setChecked(true); - _showTagsAction = UIUtils::createAction(this, "&Show tags", FILE_ICON_EDITOR_SHOWTAGS, SLOT(showTags()), "Show tags on items"); - _showTagsAction->setCheckable(true); - _showTagsAction->setChecked(true); - _snapToEdgesAction = UIUtils::createAction(this, "Sna&p to edges", FILE_ICON_EDITOR_SNAPTOEDGES, SLOT(snapToEdges()), "Snap items to the image edges"); - _snapToEdgesAction->setCheckable(true); - _snapToEdgesAction->setChecked(true); - - _zoomFitAction = UIUtils::createAction(this, "Zoom to &window", FILE_ICON_ZOOMFIT, SLOT(fitImageToWindow()), "Zoom the view to fit the image to its window", "Ctrl+Alt+A"); - - _copyDraftItems = UIUtils::createAction(this, "&Copy item(s)", FILE_ICON_COPY, SLOT(copyDraftItems()), "Copy the selected items to the clipboard"); - _copyDraftItems->setShortcut(QKeySequence{QKeySequence::Copy}); - _pasteDraftItems = UIUtils::createAction(this, "&Paste item(s)", FILE_ICON_PASTE, SLOT(pasteDraftItems()), "Paste items from the clipboard"); - _pasteDraftItems->setShortcut(QKeySequence{QKeySequence::Paste}); - _cutDraftItems = UIUtils::createAction(this, "Cu&t item(s)", FILE_ICON_CUT, SLOT(cutDraftItems()), "Copy the selected items to the clipboard and remove them afterwards"); - _cutDraftItems->setShortcut(QKeySequence{QKeySequence::Cut}); - _convertDraftItemsAction = UIUtils::createAction(this, "&Convert to pixels", FILE_ICON_EDITOR_CONVERTTOPIXELS, SLOT(convertDraftItems()), "Converts the selected items to pixels", "Return"); - - // Listen for clipboard changes to update our actions - connect(&grinder()->clipboardManager(), &ClipboardManager::dataChanged, this, &ImageEditorView::updateActions); - - updateActions(); -} - -void ImageEditorView::setEditorScene(ImageEditorScene* scene) -{ - if (_editorScene) - { - disconnect(_editorScene, nullptr, this, nullptr); - disconnect(&_editorScene->imageEditor()->editorTools(), nullptr, this, nullptr); - disconnect(&_editorScene->imageEditor()->environment(), nullptr, this, nullptr); - } - - _editorScene = scene; - VisualSceneView::setScene(scene); - - if (_editorScene) - { - // Update the view's cursor when the active draft item in-place editor has changed - connect(_editorScene, &ImageEditorScene::draftItemInPlaceEditorActivated, this, &ImageEditorView::draftItemInPlaceEditorActivated); - connect(_editorScene, &ImageEditorScene::draftItemInPlaceEditorDeactivated, this, &ImageEditorView::draftItemInPlaceEditorDeactivated); - - // Update the view's cursor when the active image editor tool has changed - connect(&_editorScene->imageEditor()->editorTools(), &ImageEditorToolList::activeToolChanging, this, &ImageEditorView::imageEditorToolChanging); - connect(&_editorScene->imageEditor()->editorTools(), &ImageEditorToolList::activeToolChanged, this, &ImageEditorView::imageEditorToolChanged); - - // Update the view's actions when the image editor environment has changed - connect(&_editorScene->imageEditor()->environment(), &ImageEditorEnvironment::showDirectionArrowsChanged, this, &ImageEditorView::showDirectionArrowsChanged); - connect(&_editorScene->imageEditor()->environment(), &ImageEditorEnvironment::showTagsChanged, this, &ImageEditorView::showTagsChanged); - connect(&_editorScene->imageEditor()->environment(), &ImageEditorEnvironment::snapToEdgesChanged, this, &ImageEditorView::snapToEdgesChanged); - } -} - -void ImageEditorView::removeSelectedItems() const -{ - if (_editorScene) - _editorScene->imageEditor()->controller().removeSelectedNodes(); -} - -void ImageEditorView::resizeSelectedItems(QSize delta) -{ - if (_editorScene) - { - for (auto item : _editorScene->getNodes<DraftItemNode>(true)) - { - if (auto draftItem = item->draftItem().lock()) // Make sure the underlying draft item still exists - { - if (auto sizeProperty = draftItem->properties().property<SizeProperty>(PropertyID::Size)) - sizeProperty->setValue(*sizeProperty + delta); - } - } - } -} - -void ImageEditorView::prepareNodeContextMenu(QMenu& menu) const -{ - QAction* firstAction = !menu.actions().isEmpty() ? menu.actions().first() : nullptr; - - menu.insertAction(firstAction, _cutDraftItems); - menu.insertAction(firstAction, _copyDraftItems); - menu.insertAction(firstAction, _pasteDraftItems); - menu.insertSeparator(firstAction); - menu.insertAction(firstAction, _convertDraftItemsAction); -} - -void ImageEditorView::prepareNodesContextMenu(QMenu& menu) const -{ - prepareNodeContextMenu(menu); -} - -std::vector<QAction*> ImageEditorView::getActions(AddActionsMode mode) const -{ - std::vector<QAction*> actions; - - if (mode != AddActionsMode::Toolbar) - { - actions.push_back(_cutDraftItems); - actions.push_back(_copyDraftItems); - actions.push_back(_pasteDraftItems); - actions.push_back(nullptr); - - actions.push_back(_selectAllAction); - actions.push_back(_deleteSelectedAction); - actions.push_back(nullptr); - } - - actions.push_back(_snapToEdgesAction); - actions.push_back(nullptr); - actions.push_back(_showDirectionsAction); - actions.push_back(_showTagsAction); - actions.push_back(nullptr); - actions.push_back(_zoomInAction); - actions.push_back(_zoomOutAction); - actions.push_back(_zoomFullAction); - actions.push_back(_zoomFitAction); - - return actions; -} - -void ImageEditorView::drawBackground(QPainter* painter, const QRectF& rect) -{ - // Draw scale-invariant background - auto scaleFactor = painter->transform().m11(); - auto bgRect = rect; - - bgRect.setTopLeft(bgRect.topLeft() * scaleFactor); - bgRect.setBottomRight(bgRect.bottomRight() * scaleFactor); - - painter->scale(1.0 / scaleFactor, 1.0 / scaleFactor); - painter->fillRect(bgRect, backgroundBrush()); - painter->scale(scaleFactor, scaleFactor); -} - -void ImageEditorView::keyPressEvent(QKeyEvent* event) -{ - if (dynamic_cast<QGraphicsProxyWidget*>(_scene ? _scene->focusItem() : nullptr) == nullptr) // If a widget inside the scene has the input focus, do not process key events here - { - if ((event->key() == Qt::Key_Up || event->key() == Qt::Key_Down || event->key() == Qt::Key_Left || event->key() == Qt::Key_Right) && (event->modifiers() == Qt::ShiftModifier)) // Enable item resizing via cursor keys - { - QSize delta{0, 0}; - int offset = 1; - - switch (event->key()) - { - case Qt::Key_Left: - delta.setWidth(-offset); - break; - - case Qt::Key_Right: - delta.setWidth(offset); - break; - - case Qt::Key_Up: - delta.setHeight(-offset); - break; - - case Qt::Key_Down: - delta.setHeight(offset); - break; - - default: - break; - } - - if (!delta.isNull()) - { - resizeSelectedItems(delta); - return; - } - } - } - - VisualSceneView::keyPressEvent(event); -} - -void ImageEditorView::updateActions() -{ - VisualSceneView::updateActions(); - - bool draftItemSelected = false; - bool editableItemSelected = false; - bool activeLayerEditable = false; - - if (_editorScene) - { - auto selectedNodes = _editorScene->getNodes<DraftItemNode>(true); - draftItemSelected = !selectedNodes.empty(); - - for (const auto& selectedNode : selectedNodes) - { - if (!selectedNode->isLocked()) - { - editableItemSelected = true; - break; - } - } - - auto activeLayer = _editorScene->imageEditor()->controller().activeLayer(); - activeLayerEditable = activeLayer ? !activeLayer->hasFlag(Layer::Flag::Locked) : false; - } - - _zoomFitAction->setEnabled(_scene); - - _copyDraftItems->setEnabled(_scene && draftItemSelected); - _pasteDraftItems->setEnabled(_scene && grinder()->clipboardManager().hasData(DraftItemVector::Serialization_Element) && activeLayerEditable); - _cutDraftItems->setEnabled(_scene && draftItemSelected && editableItemSelected); - _convertDraftItemsAction->setEnabled(_scene && draftItemSelected && editableItemSelected); - _deleteSelectedAction->setEnabled(_deleteSelectedAction->isEnabled() && draftItemSelected && editableItemSelected); -} - -void ImageEditorView::updateCursor(const QCursor& cursor) -{ - setCursor(cursor); - viewport()->setCursor(cursor); -} - -void ImageEditorView::showDirectionArrows() -{ - if (_editorScene) - _editorScene->imageEditor()->environment().setShowDirectionArrows(_showDirectionsAction->isChecked()); -} - -void ImageEditorView::showTags() -{ - if (_editorScene) - _editorScene->imageEditor()->environment().setShowTags(_showTagsAction->isChecked()); -} - -void ImageEditorView::snapToEdges() -{ - if (_editorScene) - _editorScene->imageEditor()->environment().setSnapToEdges(_snapToEdgesAction->isChecked()); -} - -void ImageEditorView::fitImageToWindow() -{ - if (_editorScene) - _editorScene->fitViewToImage(); -} - -void ImageEditorView::imageEditorToolChanging(ImageEditorTool* oldTool, ImageEditorTool* newTool) -{ - if (oldTool) - disconnect(oldTool, &ImageEditorTool::changeCursor, this, &ImageEditorView::updateCursor); - - if (newTool) - connect(newTool, &ImageEditorTool::changeCursor, this, &ImageEditorView::updateCursor); -} - -void ImageEditorView::imageEditorToolChanged(ImageEditorTool* tool) -{ - updateCursor(tool->getCursor()); - - if (_editorScene) - _editorScene->clearSelection(); -} - -void ImageEditorView::draftItemInPlaceEditorActivated(InPlaceEditorBase* editor) -{ - connect(editor, &InPlaceEditorBase::changeCursor, this, &ImageEditorView::updateCursor); -} - -void ImageEditorView::draftItemInPlaceEditorDeactivated(InPlaceEditorBase* editor) -{ - disconnect(editor, &InPlaceEditorBase::changeCursor, this, &ImageEditorView::updateCursor); -} - -void ImageEditorView::showDirectionArrowsChanged(bool show) -{ - _showDirectionsAction->setChecked(show); - update(); -} - -void ImageEditorView::showTagsChanged(bool show) -{ - _showTagsAction->setChecked(show); - update(); -} - -void ImageEditorView::snapToEdgesChanged(bool snap) -{ - _snapToEdgesAction->setChecked(snap); - update(); -} - -void ImageEditorView::copyDraftItems() const -{ - if (_editorScene) - _editorScene->imageEditor()->controller().copySelectedNodes(); -} - -void ImageEditorView::pasteDraftItems() const -{ - if (_editorScene) - _editorScene->imageEditor()->controller().pasteSelectedNodes(); -} - -void ImageEditorView::cutDraftItems() const -{ - if (_editorScene) - _editorScene->imageEditor()->controller().cutSelectedNodes(); -} - -void ImageEditorView::convertDraftItems() const -{ - if (_editorScene) - _editorScene->imageEditor()->controller().convertSelectedNodesToPixels(); -} +/****************************************************************************** + * File: ImageEditorView.cpp + * Date: 15.3.2018 + *****************************************************************************/ + +#include "Grinder.h" +#include "ImageEditorView.h" +#include "ImageEditorScene.h" +#include "ImageEditor.h" +#include "DraftItemNode.h" +#include "core/GrinderApplication.h" +#include "controller/ImageEditorController.h" +#include "ui/UIUtils.h" +#include "res/Resources.h" + +ImageEditorView::ImageEditorView(QWidget* parent) : VisualSceneView(parent) +{ + // Set the background of the view to a checkerboard pattern + setBackgroundBrush(QPixmap{FILE_ICON_EDITOR_BACKGROUND}); + + // Set widget border + setStyleSheet(QString{"QGraphicsView { border: 1px solid %1; }"}.arg(QPalette{}.color(QPalette::Dark).name())); + + // Create the sampling mode menu + QMenu* samplingModeMenu = new QMenu{this}; + _samplingModeLayerAction = samplingModeMenu->addAction(QIcon{FILE_ICON_LAYER}, "Layer sampling", this, SLOT(selectLayerSamplingMode())); + _samplingModeLayerAction->setCheckable(true); + _samplingModeImageAction = samplingModeMenu->addAction(QIcon{FILE_ICON_EDITOR_SAMPLING_IMAGE}, "Image sampling", this, SLOT(selectImageSamplingMode())); + _samplingModeImageAction->setCheckable(true); + + // Create view actions + _showDirectionsAction = UIUtils::createAction(this, "&Show direction arrows", FILE_ICON_EDITOR_SHOWDIRECTIONS, SLOT(showDirectionArrows()), "Show direction arrows on items"); + _showDirectionsAction->setCheckable(true); + _showDirectionsAction->setChecked(true); + _showTagsAction = UIUtils::createAction(this, "&Show tags", FILE_ICON_EDITOR_SHOWTAGS, SLOT(showTags()), "Show tags on items"); + _showTagsAction->setCheckable(true); + _showTagsAction->setChecked(true); + _snapToEdgesAction = UIUtils::createAction(this, "Sna&p to edges", FILE_ICON_EDITOR_SNAPTOEDGES, SLOT(snapToEdges()), "Snap items to the image edges"); + _snapToEdgesAction->setCheckable(true); + _snapToEdgesAction->setChecked(true); + + _zoomFitAction = UIUtils::createAction(this, "Zoom to &window", FILE_ICON_ZOOMFIT, SLOT(fitImageToWindow()), "Zoom the view to fit the image to its window", "Ctrl+Alt+A"); + + _samplingModeAction = UIUtils::createAction(this, "Sampling mode", "", SLOT(toggleSamplingMode()), "Select the sampling mode", "Ctrl+M"); + _samplingModeAction->setMenu(samplingModeMenu); + + _copyDraftItems = UIUtils::createAction(this, "&Copy item(s)", FILE_ICON_COPY, SLOT(copyDraftItems()), "Copy the selected items to the clipboard"); + _copyDraftItems->setShortcut(QKeySequence{QKeySequence::Copy}); + _pasteDraftItems = UIUtils::createAction(this, "&Paste item(s)", FILE_ICON_PASTE, SLOT(pasteDraftItems()), "Paste items from the clipboard"); + _pasteDraftItems->setShortcut(QKeySequence{QKeySequence::Paste}); + _cutDraftItems = UIUtils::createAction(this, "Cu&t item(s)", FILE_ICON_CUT, SLOT(cutDraftItems()), "Copy the selected items to the clipboard and remove them afterwards"); + _cutDraftItems->setShortcut(QKeySequence{QKeySequence::Cut}); + _convertDraftItemsAction = UIUtils::createAction(this, "&Convert to pixels", FILE_ICON_EDITOR_CONVERTTOPIXELS, SLOT(convertDraftItems()), "Converts the selected items to pixels", "Return"); + + // Listen for clipboard changes to update our actions + connect(&grinder()->clipboardManager(), &ClipboardManager::dataChanged, this, &ImageEditorView::updateActions); + + updateActions(); +} + +void ImageEditorView::setEditorScene(ImageEditorScene* scene) +{ + if (_editorScene) + { + disconnect(_editorScene, nullptr, this, nullptr); + disconnect(&_editorScene->imageEditor()->editorTools(), nullptr, this, nullptr); + disconnect(&_editorScene->imageEditor()->environment(), nullptr, this, nullptr); + } + + _editorScene = scene; + VisualSceneView::setScene(scene); + + if (_editorScene) + { + // Update the view's cursor when the active draft item in-place editor has changed + connect(_editorScene, &ImageEditorScene::draftItemInPlaceEditorActivated, this, &ImageEditorView::draftItemInPlaceEditorActivated); + connect(_editorScene, &ImageEditorScene::draftItemInPlaceEditorDeactivated, this, &ImageEditorView::draftItemInPlaceEditorDeactivated); + + // Update the view's cursor when the active image editor tool has changed + connect(&_editorScene->imageEditor()->editorTools(), &ImageEditorToolList::activeToolChanging, this, &ImageEditorView::imageEditorToolChanging); + connect(&_editorScene->imageEditor()->editorTools(), &ImageEditorToolList::activeToolChanged, this, &ImageEditorView::imageEditorToolChanged); + + // Update the view's actions when the image editor environment has changed + connect(&_editorScene->imageEditor()->environment(), &ImageEditorEnvironment::showDirectionArrowsChanged, this, &ImageEditorView::showDirectionArrowsChanged); + connect(&_editorScene->imageEditor()->environment(), &ImageEditorEnvironment::showTagsChanged, this, &ImageEditorView::showTagsChanged); + connect(&_editorScene->imageEditor()->environment(), &ImageEditorEnvironment::snapToEdgesChanged, this, &ImageEditorView::snapToEdgesChanged); + connect(&_editorScene->imageEditor()->environment(), &ImageEditorEnvironment::samplingModeChanged, this, &ImageEditorView::samplingModeChanged); + } +} + +void ImageEditorView::removeSelectedItems() const +{ + if (_editorScene) + _editorScene->imageEditor()->controller().removeSelectedNodes(); +} + +void ImageEditorView::resizeSelectedItems(QSize delta) +{ + if (_editorScene) + { + for (auto item : _editorScene->getNodes<DraftItemNode>(true)) + { + if (auto draftItem = item->draftItem().lock()) // Make sure the underlying draft item still exists + { + if (auto sizeProperty = draftItem->properties().property<SizeProperty>(PropertyID::Size)) + sizeProperty->setValue(*sizeProperty + delta); + } + } + } +} + +void ImageEditorView::prepareNodeContextMenu(QMenu& menu) const +{ + QAction* firstAction = !menu.actions().isEmpty() ? menu.actions().first() : nullptr; + + menu.insertAction(firstAction, _cutDraftItems); + menu.insertAction(firstAction, _copyDraftItems); + menu.insertAction(firstAction, _pasteDraftItems); + menu.insertSeparator(firstAction); + menu.insertAction(firstAction, _convertDraftItemsAction); +} + +void ImageEditorView::prepareNodesContextMenu(QMenu& menu) const +{ + prepareNodeContextMenu(menu); +} + +std::vector<QAction*> ImageEditorView::getActions(AddActionsMode mode) const +{ + std::vector<QAction*> actions; + + if (mode != AddActionsMode::Toolbar) + { + actions.push_back(_cutDraftItems); + actions.push_back(_copyDraftItems); + actions.push_back(_pasteDraftItems); + actions.push_back(nullptr); + + actions.push_back(_selectAllAction); + actions.push_back(_deleteSelectedAction); + actions.push_back(nullptr); + } + + actions.push_back(_samplingModeAction); + actions.push_back(nullptr); + actions.push_back(_snapToEdgesAction); + actions.push_back(nullptr); + actions.push_back(_showDirectionsAction); + actions.push_back(_showTagsAction); + actions.push_back(nullptr); + actions.push_back(_zoomInAction); + actions.push_back(_zoomOutAction); + actions.push_back(_zoomFullAction); + actions.push_back(_zoomFitAction); + + return actions; +} + +void ImageEditorView::drawBackground(QPainter* painter, const QRectF& rect) +{ + // Draw scale-invariant background + auto scaleFactor = painter->transform().m11(); + auto bgRect = rect; + + bgRect.setTopLeft(bgRect.topLeft() * scaleFactor); + bgRect.setBottomRight(bgRect.bottomRight() * scaleFactor); + + painter->scale(1.0 / scaleFactor, 1.0 / scaleFactor); + painter->fillRect(bgRect, backgroundBrush()); + painter->scale(scaleFactor, scaleFactor); +} + +void ImageEditorView::keyPressEvent(QKeyEvent* event) +{ + if (dynamic_cast<QGraphicsProxyWidget*>(_scene ? _scene->focusItem() : nullptr) == nullptr) // If a widget inside the scene has the input focus, do not process key events here + { + if ((event->key() == Qt::Key_Up || event->key() == Qt::Key_Down || event->key() == Qt::Key_Left || event->key() == Qt::Key_Right) && (event->modifiers() == Qt::ShiftModifier)) // Enable item resizing via cursor keys + { + QSize delta{0, 0}; + int offset = 1; + + switch (event->key()) + { + case Qt::Key_Left: + delta.setWidth(-offset); + break; + + case Qt::Key_Right: + delta.setWidth(offset); + break; + + case Qt::Key_Up: + delta.setHeight(-offset); + break; + + case Qt::Key_Down: + delta.setHeight(offset); + break; + + default: + break; + } + + if (!delta.isNull()) + { + resizeSelectedItems(delta); + return; + } + } + } + + VisualSceneView::keyPressEvent(event); +} + +void ImageEditorView::updateActions() +{ + VisualSceneView::updateActions(); + + bool draftItemSelected = false; + bool editableItemSelected = false; + bool activeLayerEditable = false; + + if (_editorScene) + { + auto selectedNodes = _editorScene->getNodes<DraftItemNode>(true); + draftItemSelected = !selectedNodes.empty(); + + for (const auto& selectedNode : selectedNodes) + { + if (!selectedNode->isLocked()) + { + editableItemSelected = true; + break; + } + } + + auto activeLayer = _editorScene->imageEditor()->controller().activeLayer(); + activeLayerEditable = activeLayer ? !activeLayer->hasFlag(Layer::Flag::Locked) : false; + } + + _zoomFitAction->setEnabled(_scene); + + _copyDraftItems->setEnabled(_scene && draftItemSelected); + _pasteDraftItems->setEnabled(_scene && grinder()->clipboardManager().hasData(DraftItemVector::Serialization_Element) && activeLayerEditable); + _cutDraftItems->setEnabled(_scene && draftItemSelected && editableItemSelected); + _convertDraftItemsAction->setEnabled(_scene && draftItemSelected && editableItemSelected); + _deleteSelectedAction->setEnabled(_deleteSelectedAction->isEnabled() && draftItemSelected && editableItemSelected); + + updateSamplingMode(); +} + +void ImageEditorView::updateSamplingMode() +{ + if (_editorScene) + { + auto modifyActions = [this](QString text, QString icon, bool layerChecked, bool imageChecked) { + _samplingModeAction->setText(text); + _samplingModeAction->setToolTip(text); + _samplingModeAction->setIcon(QIcon{icon}); + + _samplingModeLayerAction->setChecked(layerChecked); + _samplingModeImageAction->setChecked(imageChecked); + }; + + switch (_editorScene->imageEditor()->environment().getSamplingMode()) + { + case ImageEditorEnvironment::SamplingMode::Layer: + modifyActions("Sampling mode: Layer", FILE_ICON_LAYER, true, false); + break; + + case ImageEditorEnvironment::SamplingMode::Image: + modifyActions("Sampling mode: Image", FILE_ICON_EDITOR_SAMPLING_IMAGE, false, true); + break; + } + } +} + +void ImageEditorView::updateCursor(const QCursor& cursor) +{ + setCursor(cursor); + viewport()->setCursor(cursor); +} + +void ImageEditorView::showDirectionArrows() +{ + if (_editorScene) + _editorScene->imageEditor()->environment().setShowDirectionArrows(_showDirectionsAction->isChecked()); +} + +void ImageEditorView::showTags() +{ + if (_editorScene) + _editorScene->imageEditor()->environment().setShowTags(_showTagsAction->isChecked()); +} + +void ImageEditorView::snapToEdges() +{ + if (_editorScene) + _editorScene->imageEditor()->environment().setSnapToEdges(_snapToEdgesAction->isChecked()); +} + +void ImageEditorView::fitImageToWindow() +{ + if (_editorScene) + _editorScene->fitViewToImage(); +} + +void ImageEditorView::toggleSamplingMode() +{ + if (_editorScene) + { + switch (_editorScene->imageEditor()->environment().getSamplingMode()) + { + case ImageEditorEnvironment::SamplingMode::Layer: + selectImageSamplingMode(); + break; + + case ImageEditorEnvironment::SamplingMode::Image: + selectLayerSamplingMode(); + break; + } + } +} + +void ImageEditorView::selectLayerSamplingMode() +{ + if (_editorScene) + { + _editorScene->imageEditor()->environment().setSamplingMode(ImageEditorEnvironment::SamplingMode::Layer); + updateSamplingMode(); + } +} + +void ImageEditorView::selectImageSamplingMode() +{ + if (_editorScene) + { + _editorScene->imageEditor()->environment().setSamplingMode(ImageEditorEnvironment::SamplingMode::Image); + updateSamplingMode(); + } +} + +void ImageEditorView::imageEditorToolChanging(ImageEditorTool* oldTool, ImageEditorTool* newTool) +{ + if (oldTool) + disconnect(oldTool, &ImageEditorTool::changeCursor, this, &ImageEditorView::updateCursor); + + if (newTool) + connect(newTool, &ImageEditorTool::changeCursor, this, &ImageEditorView::updateCursor); +} + +void ImageEditorView::imageEditorToolChanged(ImageEditorTool* tool) +{ + updateCursor(tool->getCursor()); + + if (_editorScene) + _editorScene->clearSelection(); +} + +void ImageEditorView::draftItemInPlaceEditorActivated(InPlaceEditorBase* editor) +{ + connect(editor, &InPlaceEditorBase::changeCursor, this, &ImageEditorView::updateCursor); +} + +void ImageEditorView::draftItemInPlaceEditorDeactivated(InPlaceEditorBase* editor) +{ + disconnect(editor, &InPlaceEditorBase::changeCursor, this, &ImageEditorView::updateCursor); +} + +void ImageEditorView::showDirectionArrowsChanged(bool show) +{ + _showDirectionsAction->setChecked(show); + update(); +} + +void ImageEditorView::showTagsChanged(bool show) +{ + _showTagsAction->setChecked(show); + update(); +} + +void ImageEditorView::snapToEdgesChanged(bool snap) +{ + _snapToEdgesAction->setChecked(snap); + update(); +} + +void ImageEditorView::samplingModeChanged(ImageEditorEnvironment::SamplingMode mode) +{ + Q_UNUSED(mode); + + updateSamplingMode(); + update(); +} + +void ImageEditorView::copyDraftItems() const +{ + if (_editorScene) + _editorScene->imageEditor()->controller().copySelectedNodes(); +} + +void ImageEditorView::pasteDraftItems() const +{ + if (_editorScene) + _editorScene->imageEditor()->controller().pasteSelectedNodes(); +} + +void ImageEditorView::cutDraftItems() const +{ + if (_editorScene) + _editorScene->imageEditor()->controller().cutSelectedNodes(); +} + +void ImageEditorView::convertDraftItems() const +{ + if (_editorScene) + _editorScene->imageEditor()->controller().convertSelectedNodesToPixels(); +} diff --git a/Grinder/ui/image/ImageEditorView.h b/Grinder/ui/image/ImageEditorView.h index 62b9163b2e439b8935cd84c0f02df8115374d091..f77e39d1dc1b44ac9aaf5c6881c243a6caeceed1 100644 --- a/Grinder/ui/image/ImageEditorView.h +++ b/Grinder/ui/image/ImageEditorView.h @@ -1,93 +1,104 @@ -/****************************************************************************** - * File: ImageEditorView.h - * Date: 15.3.2018 - *****************************************************************************/ - -#ifndef IMAGEEDITORVIEW_H -#define IMAGEEDITORVIEW_H - -#include "ui/visscene/VisualSceneView.h" -#include "ImageEditorStyle.h" - -namespace grndr -{ - class ImageEditorScene; - class ImageEditorTool; - class InPlaceEditorBase; - - class ImageEditorView : public VisualSceneView - { - Q_OBJECT - - public: - ImageEditorView(QWidget *parent = nullptr); - - public: - ImageEditorScene* editorScene() { return _editorScene; } - const ImageEditorScene* editorScene() const { return _editorScene; } - void setEditorScene(ImageEditorScene* scene); - - public slots: - virtual void removeSelectedItems() const override; - virtual void resizeSelectedItems(QSize delta); - - public: - virtual const ImageEditorStyle& sceneStyle() const override { return ImageEditorStyle::style(); } - - virtual void prepareNodeContextMenu(QMenu& menu) const override; - virtual void prepareNodesContextMenu(QMenu& menu) const override; - - protected: - virtual std::vector<QAction*> getActions(AddActionsMode mode) const override; - - protected: - virtual void drawBackground(QPainter* painter, const QRectF& rect) override; - - virtual void keyPressEvent(QKeyEvent* event) override; - - protected slots: - virtual void updateActions() override; - - private: - void updateCursor(const QCursor& cursor); - - private slots: - void showDirectionArrows(); - void showTags(); - void snapToEdges(); - - void fitImageToWindow(); - - void imageEditorToolChanging(ImageEditorTool* oldTool, ImageEditorTool* newTool); - void imageEditorToolChanged(ImageEditorTool* tool); - - void draftItemInPlaceEditorActivated(InPlaceEditorBase* editor); - void draftItemInPlaceEditorDeactivated(InPlaceEditorBase* editor); - - void showDirectionArrowsChanged(bool show); - void showTagsChanged(bool show); - void snapToEdgesChanged(bool snap); - - void copyDraftItems() const; - void pasteDraftItems() const; - void cutDraftItems() const; - void convertDraftItems() const; - - private: - ImageEditorScene* _editorScene{nullptr}; - - private: - QAction* _showDirectionsAction{nullptr}; - QAction* _showTagsAction{nullptr}; - QAction* _snapToEdgesAction{nullptr}; - - QAction* _zoomFitAction{nullptr}; - - QAction* _copyDraftItems{nullptr}; - QAction* _pasteDraftItems{nullptr}; - QAction* _cutDraftItems{nullptr}; - QAction* _convertDraftItemsAction{nullptr}; - }; -} - -#endif +/****************************************************************************** + * File: ImageEditorView.h + * Date: 15.3.2018 + *****************************************************************************/ + +#ifndef IMAGEEDITORVIEW_H +#define IMAGEEDITORVIEW_H + +#include "ui/visscene/VisualSceneView.h" +#include "ImageEditorEnvironment.h" +#include "ImageEditorStyle.h" + +namespace grndr +{ + class ImageEditorScene; + class ImageEditorTool; + class InPlaceEditorBase; + + class ImageEditorView : public VisualSceneView + { + Q_OBJECT + + public: + ImageEditorView(QWidget *parent = nullptr); + + public: + ImageEditorScene* editorScene() { return _editorScene; } + const ImageEditorScene* editorScene() const { return _editorScene; } + void setEditorScene(ImageEditorScene* scene); + + public slots: + virtual void removeSelectedItems() const override; + virtual void resizeSelectedItems(QSize delta); + + public: + virtual const ImageEditorStyle& sceneStyle() const override { return ImageEditorStyle::style(); } + + virtual void prepareNodeContextMenu(QMenu& menu) const override; + virtual void prepareNodesContextMenu(QMenu& menu) const override; + + protected: + virtual std::vector<QAction*> getActions(AddActionsMode mode) const override; + + protected: + virtual void drawBackground(QPainter* painter, const QRectF& rect) override; + + virtual void keyPressEvent(QKeyEvent* event) override; + + protected slots: + virtual void updateActions() override; + + private: + void updateSamplingMode(); + void updateCursor(const QCursor& cursor); + + private slots: + void showDirectionArrows(); + void showTags(); + void snapToEdges(); + + void fitImageToWindow(); + + void toggleSamplingMode(); + void selectLayerSamplingMode(); + void selectImageSamplingMode(); + + void imageEditorToolChanging(ImageEditorTool* oldTool, ImageEditorTool* newTool); + void imageEditorToolChanged(ImageEditorTool* tool); + + void draftItemInPlaceEditorActivated(InPlaceEditorBase* editor); + void draftItemInPlaceEditorDeactivated(InPlaceEditorBase* editor); + + void showDirectionArrowsChanged(bool show); + void showTagsChanged(bool show); + void snapToEdgesChanged(bool snap); + void samplingModeChanged(ImageEditorEnvironment::SamplingMode); + + void copyDraftItems() const; + void pasteDraftItems() const; + void cutDraftItems() const; + void convertDraftItems() const; + + private: + ImageEditorScene* _editorScene{nullptr}; + + private: + QAction* _showDirectionsAction{nullptr}; + QAction* _showTagsAction{nullptr}; + QAction* _snapToEdgesAction{nullptr}; + + QAction* _zoomFitAction{nullptr}; + + QAction* _samplingModeAction{nullptr}; + QAction* _samplingModeLayerAction{nullptr}; + QAction* _samplingModeImageAction{nullptr}; + + QAction* _copyDraftItems{nullptr}; + QAction* _pasteDraftItems{nullptr}; + QAction* _cutDraftItems{nullptr}; + QAction* _convertDraftItemsAction{nullptr}; + }; +} + +#endif diff --git a/Grinder/ui/image/tools/ColorPickerTool.cpp b/Grinder/ui/image/tools/ColorPickerTool.cpp index 19e78a0b72c03fd7ba0cd500931699754f8acc99..83f7cf86f350a4dfe9f727f45e4900b29188dace 100644 --- a/Grinder/ui/image/tools/ColorPickerTool.cpp +++ b/Grinder/ui/image/tools/ColorPickerTool.cpp @@ -1,62 +1,62 @@ -/****************************************************************************** - * File: ColorPickerTool.cpp - * Date: 22.3.2018 - *****************************************************************************/ - -#include "Grinder.h" -#include "ColorPickerTool.h" -#include "ui/image/ImageEditor.h" -#include "res/Resources.h" - -const char* ColorPickerTool::tool_type = "ColorPickerTool"; - -ColorPickerTool::ColorPickerTool(ImageEditor* imageEditor) : ImageEditorTool(imageEditor, "Color picker", FILE_ICON_EDITOR_COLORPICKER, "K", QCursor{QPixmap{FILE_CURSOR_EDITOR_COLORPICKER}, 0, 23}) -{ - -} - -void ColorPickerTool::toolActivated(ImageEditorTool* prevTool) -{ - ImageEditorTool::toolActivated(prevTool); - - _previousTool = prevTool; - _switchToPreviousTool = false; -} - -ImageEditorTool::InputEventResult ColorPickerTool::mousePressed(const QGraphicsSceneMouseEvent* event) -{ - if (event->widget()) - { - // Grab the current scene display and get the pixel color under the cursor - auto pixmap = event->widget()->grab(); - auto image = pixmap.toImage(); - auto pos = event->widget()->mapFromGlobal(event->screenPos()); - - // Set the grabbed color as the primary one and switch back to the previous tool - _imageEditor->environment().setPrimaryColor(image.pixelColor(pos)); - _switchToPreviousTool = true; - } - - return InputEventResult::Process; -} - -ImageEditorTool::InputEventResult ColorPickerTool::mouseMoved(const QGraphicsSceneMouseEvent* event) -{ - Q_UNUSED(event); - return InputEventResult::Process; -} - -ImageEditorTool::InputEventResult ColorPickerTool::mouseReleased(const QGraphicsSceneMouseEvent* event) -{ - Q_UNUSED(event); - - if (_switchToPreviousTool) - { - if (_previousTool) - _imageEditor->editorTools().activateTool(_previousTool->getToolType()); - - _switchToPreviousTool = false; - } - - return InputEventResult::Process; -} +/****************************************************************************** + * File: ColorPickerTool.cpp + * Date: 22.3.2018 + *****************************************************************************/ + +#include "Grinder.h" +#include "ColorPickerTool.h" +#include "ui/image/ImageEditor.h" +#include "res/Resources.h" + +const char* ColorPickerTool::tool_type = "ColorPickerTool"; + +ColorPickerTool::ColorPickerTool(ImageEditor* imageEditor) : ImageEditorTool(imageEditor, "Color picker", FILE_ICON_EDITOR_COLORPICKER, "K", QCursor{QPixmap{FILE_CURSOR_EDITOR_COLORPICKER}, 0, 23}) +{ + +} + +void ColorPickerTool::toolActivated(ImageEditorTool* prevTool) +{ + ImageEditorTool::toolActivated(prevTool); + + _previousTool = prevTool; + _switchToPreviousTool = false; +} + +ImageEditorTool::InputEventResult ColorPickerTool::mousePressed(const QGraphicsSceneMouseEvent* event) +{ + if (event->widget()) + { + // Grab the current scene display and get the pixel color under the cursor + auto pixmap = event->widget()->grab(); + auto image = pixmap.toImage(); + auto pos = event->widget()->mapFromGlobal(event->screenPos()); + + // Set the grabbed color as the primary one and switch back to the previous tool + _imageEditor->environment().setPrimaryColor(image.pixelColor(pos)); + _switchToPreviousTool = true; + } + + return InputEventResult::Process; +} + +ImageEditorTool::InputEventResult ColorPickerTool::mouseMoved(const QGraphicsSceneMouseEvent* event) +{ + Q_UNUSED(event); + return InputEventResult::Process; +} + +ImageEditorTool::InputEventResult ColorPickerTool::mouseReleased(const QGraphicsSceneMouseEvent* event) +{ + Q_UNUSED(event); + + if (_switchToPreviousTool) + { + if (_previousTool) + _imageEditor->editorTools().activateTool(_previousTool->getToolType()); + + _switchToPreviousTool = false; + } + + return InputEventResult::Process; +} diff --git a/Grinder/ui/image/tools/EraserTool.cpp b/Grinder/ui/image/tools/EraserTool.cpp index b3fc73f7a039f2c82481505685eebe767a5b32a5..149ef2d248f9ce505a338ec179dee202e6d354a2 100644 --- a/Grinder/ui/image/tools/EraserTool.cpp +++ b/Grinder/ui/image/tools/EraserTool.cpp @@ -1,72 +1,72 @@ -/****************************************************************************** - * File: EraserTool.cpp - * Date: 21.5.2018 - *****************************************************************************/ - -#include "Grinder.h" -#include "EraserTool.h" -#include "ui/image/ImageEditor.h" -#include "ui/visscene/RubberBandNode.h" -#include "image/ImageUtils.h" -#include "res/Resources.h" - -const char* EraserTool::tool_type = "EraserTool"; - -EraserTool::EraserTool(ImageEditor* imageEditor) : PaintbrushTool(imageEditor, "Eraser", FILE_ICON_EDITOR_ERASER, "E", QCursor{QPixmap{FILE_CURSOR_EDITOR_ERASER}, 12, 12}) -{ - _eraseByDefault = true; -} - -void EraserTool::createProperties() -{ - PaintbrushTool::createProperties(); - - // The eraser should have a default width equal to the cursor size - setPropertyGroup("General"); - - penWidth()->setValue(_cursor.pixmap().width()); - penWidth()->setName("Eraser width"); - penWidth()->setDescription("The width of the eraser in (screen) pixels."); -} - -VisualSceneInputHandler::InputEventResult EraserTool::rightMousePressed(const QGraphicsSceneMouseEvent* event) -{ - createRubberBand(event->scenePos()); - return InputEventResult::Process; -} - -VisualSceneInputHandler::InputEventResult EraserTool::rightMouseMoved(const QGraphicsSceneMouseEvent* event) -{ - if (_rubberBandNode) - { - _rubberBandNode->move(event->scenePos()); - return InputEventResult::Process; - } - else - return InputEventResult::Ignore; -} - -VisualSceneInputHandler::InputEventResult EraserTool::rightMouseReleased(const QGraphicsSceneMouseEvent* event) -{ - Q_UNUSED(event); - - if (_rubberBandNode) - { - // Erase all pixels within the rubber band rectangle - auto eraseRect = _rubberBandNode->getRect(); - std::list<QPoint> points; - - for (int r = eraseRect.top(); r <= eraseRect.bottom(); ++r) - { - for (int c = eraseRect.left(); c <= eraseRect.right(); ++c) - points.emplace_back(c, r); - } - - _imageEditor->controller().paintPixels(nullptr, points, QColor{}); - - removeRubberBand(); - return InputEventResult::Process; - } - else - return InputEventResult::Ignore; -} +/****************************************************************************** + * File: EraserTool.cpp + * Date: 21.5.2018 + *****************************************************************************/ + +#include "Grinder.h" +#include "EraserTool.h" +#include "ui/image/ImageEditor.h" +#include "ui/visscene/RubberBandNode.h" +#include "image/ImageUtils.h" +#include "res/Resources.h" + +const char* EraserTool::tool_type = "EraserTool"; + +EraserTool::EraserTool(ImageEditor* imageEditor) : PaintbrushTool(imageEditor, "Eraser", FILE_ICON_EDITOR_ERASER, "E", QCursor{QPixmap{FILE_CURSOR_EDITOR_ERASER}, 12, 12}) +{ + _eraseByDefault = true; +} + +void EraserTool::createProperties() +{ + PaintbrushTool::createProperties(); + + // The eraser should have a default width equal to the cursor size + setPropertyGroup("General"); + + penWidth()->setValue(_cursor.pixmap().width()); + penWidth()->setName("Eraser width"); + penWidth()->setDescription("The width of the eraser in (screen) pixels."); +} + +VisualSceneInputHandler::InputEventResult EraserTool::rightMousePressed(const QGraphicsSceneMouseEvent* event) +{ + createRubberBand(event->scenePos()); + return InputEventResult::Process; +} + +VisualSceneInputHandler::InputEventResult EraserTool::rightMouseMoved(const QGraphicsSceneMouseEvent* event) +{ + if (_rubberBandNode) + { + _rubberBandNode->move(event->scenePos()); + return InputEventResult::Process; + } + else + return InputEventResult::Ignore; +} + +VisualSceneInputHandler::InputEventResult EraserTool::rightMouseReleased(const QGraphicsSceneMouseEvent* event) +{ + Q_UNUSED(event); + + if (_rubberBandNode) + { + // Erase all pixels within the rubber band rectangle + auto eraseRect = _rubberBandNode->getRect(); + std::list<QPoint> points; + + for (int r = eraseRect.top(); r <= eraseRect.bottom(); ++r) + { + for (int c = eraseRect.left(); c <= eraseRect.right(); ++c) + points.emplace_back(c, r); + } + + _imageEditor->controller().paintPixels(nullptr, points, QColor{}); + + removeRubberBand(); + return InputEventResult::Process; + } + else + return InputEventResult::Ignore; +} diff --git a/Grinder/ui/image/tools/EraserTool.h b/Grinder/ui/image/tools/EraserTool.h index 2946b0a74867d6a4228619298e3f95e8f5984c6a..500de2f2c52fbc2fc7ee26196e406214eb01616e 100644 --- a/Grinder/ui/image/tools/EraserTool.h +++ b/Grinder/ui/image/tools/EraserTool.h @@ -1,36 +1,36 @@ -/****************************************************************************** - * File: EraserTool.h - * Date: 21.5.2018 - *****************************************************************************/ - -#ifndef ERASERTOOL_H -#define ERASERTOOL_H - -#include "PaintbrushTool.h" - -namespace grndr -{ - class EraserTool : public PaintbrushTool - { - Q_OBJECT - - public: - static const char* tool_type; - - public: - EraserTool(ImageEditor* imageEditor); - - public: - virtual QString getToolType() const override { return tool_type; } - - protected: - virtual void createProperties() override; - - protected: - virtual InputEventResult rightMousePressed(const QGraphicsSceneMouseEvent* event) override; - virtual InputEventResult rightMouseMoved(const QGraphicsSceneMouseEvent* event) override; - virtual InputEventResult rightMouseReleased(const QGraphicsSceneMouseEvent* event) override; - }; -} - -#endif +/****************************************************************************** + * File: EraserTool.h + * Date: 21.5.2018 + *****************************************************************************/ + +#ifndef ERASERTOOL_H +#define ERASERTOOL_H + +#include "PaintbrushTool.h" + +namespace grndr +{ + class EraserTool : public PaintbrushTool + { + Q_OBJECT + + public: + static const char* tool_type; + + public: + EraserTool(ImageEditor* imageEditor); + + public: + virtual QString getToolType() const override { return tool_type; } + + protected: + virtual void createProperties() override; + + protected: + virtual InputEventResult rightMousePressed(const QGraphicsSceneMouseEvent* event) override; + virtual InputEventResult rightMouseMoved(const QGraphicsSceneMouseEvent* event) override; + virtual InputEventResult rightMouseReleased(const QGraphicsSceneMouseEvent* event) override; + }; +} + +#endif diff --git a/Grinder/ui/image/tools/SelectionTool.cpp b/Grinder/ui/image/tools/SelectionTool.cpp index aaebe1916b5524bcfd595d05a9d14cb46f4f5fad..114697f8c3a33d30b06fb6efaa104125ae7e12ce 100644 --- a/Grinder/ui/image/tools/SelectionTool.cpp +++ b/Grinder/ui/image/tools/SelectionTool.cpp @@ -33,6 +33,9 @@ void SelectionTool::toolActivated(ImageEditorTool* prevTool) // Listen to layer switching in order to hide any active selection connect(&_imageEditor->controller(), &ImageEditorController::layerSwitching, this, &SelectionTool::layerSwitching); + // Reset the current selection when changing the sampling mode + connect(&_imageEditor->environment(), &ImageEditorEnvironment::samplingModeChanged, this, &SelectionTool::resetSelection); + // Install an event filter to catch all key events in the application to properly handle control and shift states grinder()->installEventFilter(this); } @@ -287,7 +290,9 @@ void SelectionTool::processSelection() { if (auto layer = _imageEditor->controller().activeLayer()) { - _selection.trimSelection(layer); + if (_imageEditor->environment().getSamplingMode() == ImageEditorEnvironment::SamplingMode::Layer) + _selection.trimSelection(layer); + _selection.createSelectionNode(layer); } } diff --git a/Grinder/ui/image/tools/WandSelectionTool.cpp b/Grinder/ui/image/tools/WandSelectionTool.cpp index 005a26aa0174341d36bfc9b134c209c7dc261554..ccfd2a3cceb806360636d033eaa252f04d3530ff 100644 --- a/Grinder/ui/image/tools/WandSelectionTool.cpp +++ b/Grinder/ui/image/tools/WandSelectionTool.cpp @@ -134,12 +134,17 @@ void WandSelectionTool::createSelectionFromSeeds(bool handleReplace) { if (auto layer = _imageEditor->controller().activeLayer()) { + cv::Mat sourceImage; + + if (_imageEditor->environment().getSamplingMode() == ImageEditorEnvironment::SamplingMode::Image) + sourceImage = _imageEditor->getDisplayedImage(); + LayerPixelsData::Selection selection; bool initialized = false; for (const auto& seed : _seeds) { - auto seedSelection = _wandAlgorithm->execute(seed, *layer); + auto seedSelection = _wandAlgorithm->execute(seed, *layer, sourceImage); if (!initialized) {