Skip to content

Commit

Permalink
Optimize flood fill of when expanding the target image
Browse files Browse the repository at this point in the history
This treats everything outside the minimally bounded target image
as a single region (which it is). compareColor only needs to be
called once, and if it's true, then all of the transparent "border"
region can be filled at once. Ideally this would be done with a
rectangular fill with a mask when creating replaceImage, but
unforunately that information needs to be present in filledPixels
if expandFill is run. Thankfull this is pretty fast anyway.
  • Loading branch information
scribblemaniac committed Apr 13, 2022
1 parent ab4b5c4 commit 8c0fabf
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 16 deletions.
57 changes: 42 additions & 15 deletions core_lib/src/graphics/bitmap/bitmapimage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -800,8 +800,10 @@ bool BitmapImage::floodFill(BitmapImage** replaceImage,
int tolerance,
const int expandValue)
{
QRect fillBounds = targetImage->mBounds;
// Fill region must be 1 pixel larger than the target image to fill regions on the edge connected only by transparent pixels
QRect fillBounds = targetImage->mBounds.adjusted(-1, -1, 1, 1);
QRect maxBounds = cameraRect.united(fillBounds).adjusted(-expandValue, -expandValue, expandValue, expandValue);
const int maxWidth = maxBounds.width(), left = maxBounds.left(), top = maxBounds.top();

// If the point we are supposed to fill is outside the max bounds, do nothing
if(!maxBounds.contains(point))
Expand All @@ -813,31 +815,42 @@ bool BitmapImage::floodFill(BitmapImage** replaceImage,
tolerance = static_cast<int>(qPow(tolerance, 2));

QRect newBounds;
bool *filledPixels = floodFillPoints(targetImage, fillBounds, maxBounds, point, tolerance, newBounds);
bool shouldFillBorder = false;
bool *filledPixels = floodFillPoints(targetImage, fillBounds, maxBounds, point, tolerance, newBounds, shouldFillBorder);

const QRect& expandRect = newBounds.adjusted(-expandValue, -expandValue, expandValue, expandValue);
QRect translatedSearchBounds = newBounds.translated(-maxBounds.topLeft());

if (shouldFillBorder)
{
for (int y = 0; y < maxBounds.height(); y++)
{
for (int x = 0; x < maxBounds.width(); x++)
{
if(!translatedSearchBounds.contains(x, y))
{
filledPixels[y*maxWidth+x] = true;
}
}
}
newBounds = maxBounds;
}

// The scanned bounds should take the expansion into account
const QRect& expandRect = newBounds.adjusted(-expandValue, -expandValue, expandValue, expandValue);
if (expandValue > 0) {
newBounds = expandRect;
}

// The new bounds was smaller than the max, so set the size to maxBounds
// This ensures that the expanded area is not doubled.
// because the initial maxBounds is already expanded
if (!maxBounds.contains(newBounds)) {
newBounds = maxBounds;
}
translatedSearchBounds = newBounds.translated(-maxBounds.topLeft());

*replaceImage = new BitmapImage(newBounds, Qt::transparent);

const int maxWidth = maxBounds.width(), left = maxBounds.left(), top = maxBounds.top();

const QRect& translatedSearchBounds = newBounds.translated(-maxBounds.topLeft());
if (expandValue > 0) {
expandFill(filledPixels, translatedSearchBounds, maxBounds, expandValue);
}

*replaceImage = new BitmapImage(newBounds, Qt::transparent);

// Fill all the found pixels
for (int y = translatedSearchBounds.top(); y <= translatedSearchBounds.bottom(); y++)
{
Expand All @@ -864,10 +877,12 @@ bool* BitmapImage::floodFillPoints(const BitmapImage* targetImage,
const QRect& maxBounds,
QPoint point,
const int tolerance,
QRect& newBounds)
QRect& newBounds,
bool& fillBorder)
{
QRgb oldColor = targetImage->constScanLine(point.x(), point.y());
oldColor = qRgba(qRed(oldColor), qGreen(oldColor), qBlue(oldColor), qAlpha(oldColor));
QRect borderBounds = searchBounds.adjusted(1,1,-1,-1);

// Preparations
QList<QPoint> queue; // queue all the pixels of the filled area (as they are found)
Expand All @@ -879,11 +894,20 @@ bool* BitmapImage::floodFillPoints(const BitmapImage* targetImage,
bool spanLeft = false;
bool spanRight = false;

if (!searchBounds.contains(point))
{
// If point is outside the search area, move it anywhere in the 1px transparent border
point = searchBounds.topLeft();
}

queue.append(point);
// Preparations END

bool *filledPixels = new bool[maxBounds.height()*maxBounds.width()]{};

// True if the algorithm has attempted to fill a pixel outside the search bounds
bool checkOutside = false;

BlitRect blitBounds(point);
while (!queue.empty())
{
Expand All @@ -898,8 +922,8 @@ bool* BitmapImage::floodFillPoints(const BitmapImage* targetImage,
int yCoord = point.y() - maxBounds.top();

// In case we fill outside the searchBounds, expand the search area to the max.
if (!searchBounds.contains(xCoord, yCoord)) {
searchBounds = maxBounds;
if (!borderBounds.contains(point)) {
checkOutside = true;
}

if (filledPixels[yCoord*maxBounds.width()+xCoord]) continue;
Expand Down Expand Up @@ -947,7 +971,10 @@ bool* BitmapImage::floodFillPoints(const BitmapImage* targetImage,
}
}

fillBorder = checkOutside && compareColor(Qt::transparent, oldColor, tolerance, cache.data());

newBounds = blitBounds;

return filledPixels;
}

Expand Down
3 changes: 2 additions & 1 deletion core_lib/src/graphics/bitmap/bitmapimage.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ class BitmapImage : public KeyFrame
QRect searchBounds, const QRect& maxBounds,
QPoint point,
const int tolerance,
QRect& newBounds);
QRect& newBounds,
bool &fillBorder);
static void expandFill(bool* fillPixels, const QRect& searchBounds, const QRect& maxBounds, int expand);

void drawLine(QPointF P1, QPointF P2, QPen pen, QPainter::CompositionMode cm, bool antialiasing);
Expand Down

0 comments on commit 8c0fabf

Please sign in to comment.