Search code examples
pythonopencvflood-fill

Toggling of floodfill colors (either black or white depending on the pixel color selected)


I was writing a simple program that gradually fills (using floodfill) an image (black background with random white rectangles) to become totally white by clicking different areas of the image. Completed without a hitch.

So I thought of making it more interesting by toggling between filling with white color and black color. As in, if the pixel I click on is part of a white region, fill it to become black. Otherwise if it is part of a black region, fill it to become white.

However, after I change some boxes to become white, it refuses to change to black after subsequent clicking (unable to toggle the color back). Furthermore, because my rectangles are drawn using 3 or 4 pixels thick lines, after changing all the lines to black, it seems to still 'remember' that those lines exists, such that when I click on certain dark regions, occasionally the region (bounded by those invisible 'previous' lines) would become white color.

I have tried printing the pixel color to confirm that the color picked up is indeed white or black, but yet the floodfill is not filling it with the correct alternate color (written by my if/else loops)

import numpy as np
import cv2 as cv
import random

width = 800
height = 500
img = np.zeros((height, width), np.uint8)
mask = np.zeros((height+2, width+2), np.uint8)


def click_event(event, x, y, flags, param):
    if event == cv.EVENT_LBUTTONDOWN:
        font = cv.FONT_HERSHEY_PLAIN
        strxy = "X: {0}  Y: {1}".format(x,y)
        print(strxy)
        fillmeup(x, y)
        cv.imshow("test", img)

def fillmeup(x, y):
    print(img[y,x])
    if img[y,x] == 0:
        cv.floodFill(img, mask, (x, y), 255)

    elif img[y,x] == 255:
        cv.floodFill(img, mask, (x, y), 0)

def drawboxes(qty):
    global img
    for _ in range(qty):
        w = int(random.random()*width)
        h = int(random.random()*height)
        x = random.randrange(0, width-w)
        y = random.randrange(0, height-h)
        img = cv.rectangle(img, (x, y), (x+w, y+h), 255, 2)

drawboxes(7)

cv.imshow("test", img)
cv.setMouseCallback("test", click_event)

cv.waitKey(0)
cv.destroyAllWindows() 

Well, I would expect that every subsequent click on a black region would produce white, and vice versa, but it is not happening. And when it does switch back to white, it seems to be bounded by invisible lines that have been turned black already.

Below is an attached sample outcome. 01_start

02_selecting 2 boxes

03_selecting one of the thin white lines changes them to black: correct outcome

04_selecting some random random black space, but yet bounded white rectangles appear. the boundaries were the original white lines. weird outcome


Solution

  • floodFill() updates not only the image but also the mask.

    On output, pixels in the mask corresponding to filled pixels in the image are set to 1 or to the a value specified in flags as described below. It is therefore possible to use the same mask in multiple calls to the function to make sure the filled areas do not overlap.

    def fillmeup(x, y):
        print(img[y,x])
        mask = np.zeros((height+2, width+2), np.uint8)
        if img[y,x] == 0:
           cv.floodFill(img, mask, (x, y), 255)
        else:
           cv.floodFill(img, mask, (x, y), 0)
    

    This works for me as you describe. If you do not need mask at all, you possibly can write

    cv.floodFill(img, None, (x, y), ...)
    

    This works for me too, but I didn't find any evidence that None mask argument is legal for floodFill(). Please notify my to update the answer if you find if it is legal or not in any authoritative source.