Skip to content
Snippets Groups Projects
Commit be39d45d authored by Daniel Müller's avatar Daniel Müller
Browse files

* Replaced the Flood Fill algorithm by a faster one

parent 3ff908e1
No related branches found
No related tags found
No related merge requests found
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
#define GRNDR_VERSION_MAJOR 0 #define GRNDR_VERSION_MAJOR 0
#define GRNDR_VERSION_MINOR 6 #define GRNDR_VERSION_MINOR 6
#define GRNDR_VERSION_REVISION 0 #define GRNDR_VERSION_REVISION 0
#define GRNDR_VERSION_BUILD 218 #define GRNDR_VERSION_BUILD 219
namespace grndr namespace grndr
{ {
......
...@@ -47,7 +47,11 @@ QColor CVUtils::colorToGrayscale(QColor color) ...@@ -47,7 +47,11 @@ QColor CVUtils::colorToGrayscale(QColor color)
bool CVUtils::compareColors(QColor clr1, QColor clr2, float tolerance) bool CVUtils::compareColors(QColor clr1, QColor clr2, float tolerance)
{ {
if (tolerance > 0.0f) if (tolerance >= 1.0f)
{
return true;
}
else if (tolerance > 0.0f)
{ {
// Based on https://www.compuphase.com/cmetric.htm // Based on https://www.compuphase.com/cmetric.htm
auto rmean = (clr1.red() + clr2.red() ) / 2; auto rmean = (clr1.red() + clr2.red() ) / 2;
......
...@@ -20,38 +20,97 @@ void FloodFill::execute() ...@@ -20,38 +20,97 @@ void FloodFill::execute()
if (_fromColor == _toColor) if (_fromColor == _toColor)
return; return;
// Use a stack instead of recursion to prevent overflows scanlineFill(_seedPos.x(), _seedPos.y());
std::stack<QPoint> stack; }
stack.push(_seedPos);
void FloodFill::scanlineFill(int x, int y)
{
// Based on Paul Heckbert's Seed Fill Algorithm (crude & ugly, but reasonably fast)
struct LineSegment { int y, xl, xr, dy; };
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](QColor pixelColor) {
if (pixelColor.rgb() == _toColor.rgb())
return false;
return ((_fromColor.isValid() && CVUtils::compareColors(pixelColor, _fromColor, _tolerance)) || (!_fromColor.isValid() && pixelColor.alpha() == 0));
};
push(y, x, x, 1);
push(y + 1, x, x, -1);
while (!stack.empty()) while (!stack.empty())
{ {
auto pos = stack.top(); auto lineSegment = stack.top();
stack.pop(); stack.pop();
auto pixel = _pixels.at(pos); int y = lineSegment.y + lineSegment.dy;
auto pixelColor = pixel.get(); int l;
if (((_fromColor.isValid() && CVUtils::compareColors(pixelColor, _fromColor, _tolerance)) || (!_fromColor.isValid() && pixelColor.alpha() == 0)) && pixelColor != _toColor) for (x = lineSegment.xl; x >= 0; --x)
{ {
if (_toColor.isValid()) auto pixel = _pixels.at(y, x);
pixel = _toColor; auto pixelColor = pixel.get();
if (checkColor(pixelColor))
pixel = _toColor.isValid() ? _toColor : QColor{0, 0, 0, 0};
else else
pixel = QColor{0, 0, 0, 0}; break;
}
bool skip = false;
if (x >= lineSegment.xl)
skip = true;
if (!skip)
{
l = x + 1;
for (int deltaX = -1; deltaX <= 1; ++deltaX) if (l < lineSegment.xl)
push(y, l, lineSegment.xl - 1, -lineSegment.dy);
x = lineSegment.xl + 1;
}
do
{
if (!skip)
{ {
for (int deltaY = -1; deltaY <= 1; ++deltaY) for (; x < _matrix.cols; ++x)
{ {
if (deltaX != 0 || deltaY != 0) auto pixel = _pixels.at(y, x);
{ auto pixelColor = pixel.get();
auto newPos = QPoint{pos.x() + deltaX, pos.y() + deltaY};
if (newPos.x() >= 0 && newPos.y() >= 0 && newPos.x() < _matrix.cols && newPos.y() < _matrix.rows) if (checkColor(pixelColor))
stack.push(newPos); pixel = _toColor.isValid() ? _toColor : QColor{0, 0, 0, 0};
} 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(pixelColor))
break;
}
l = x;
} while (x <= lineSegment.xr);
} }
} }
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include <QColor> #include <QColor>
#include <QPoint> #include <QPoint>
#include <stack>
#include "CVAlgorithm.h" #include "CVAlgorithm.h"
#include "cv/Pixel.h" #include "cv/Pixel.h"
...@@ -22,6 +23,9 @@ namespace grndr ...@@ -22,6 +23,9 @@ namespace grndr
public: public:
virtual void execute() override; virtual void execute() override;
private:
void scanlineFill(int x, int y);
private: private:
QPoint _seedPos{0, 0}; QPoint _seedPos{0, 0};
QColor _fromColor; QColor _fromColor;
......
...@@ -181,7 +181,10 @@ void LayerPixelsData::fromImage(const QImage& image, QColor colorKey, float tole ...@@ -181,7 +181,10 @@ void LayerPixelsData::fromImage(const QImage& image, QColor colorKey, float tole
cv::Mat LayerPixelsData::toMatrix(QSize size, bool includeAlpha) const cv::Mat LayerPixelsData::toMatrix(QSize size, bool includeAlpha) const
{ {
if (size.isNull()) if (size.isNull())
size = bounds().size(); {
auto pixelsBounds = bounds();
size = QSize{pixelsBounds.right() + 1, pixelsBounds.bottom() + 1};
}
cv::Mat matrix = cv::Mat::zeros(size.height(), size.width(), includeAlpha ? CV_8UC4 : CV_8UC3); cv::Mat matrix = cv::Mat::zeros(size.height(), size.width(), includeAlpha ? CV_8UC4 : CV_8UC3);
PixelAccessor pixels{matrix}; PixelAccessor pixels{matrix};
...@@ -203,7 +206,10 @@ cv::Mat LayerPixelsData::toMatrix(QSize size, bool includeAlpha) const ...@@ -203,7 +206,10 @@ cv::Mat LayerPixelsData::toMatrix(QSize size, bool includeAlpha) const
QImage LayerPixelsData::toImage(QSize size, QImage* existingImage) const QImage LayerPixelsData::toImage(QSize size, QImage* existingImage) const
{ {
if (size.isNull()) if (size.isNull())
size = bounds().size(); {
auto pixelsBounds = bounds();
size = QSize{pixelsBounds.right() + 1, pixelsBounds.bottom() + 1};
}
auto imageConverter = [this](QImage* image) { auto imageConverter = [this](QImage* image) {
image->fill(0); // Erase everything with an invisible color image->fill(0); // Erase everything with an invisible color
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment