6

I am trying to write a method that will fill in a given shape so that it becomes solid black.

Example: This octagon which initially is only an outline, will turn into a solid black octagon, however this should work with any shape as long as all edges are closed.

Octagon

def img_filled(im_1, im_2):
    img_fill_neg = ImageChops.subtract(im_1, im_2)
    img_fill = ImageOps.invert(img_fill_neg)
    img_fill.show()

I have read the docs 10x over and have found several other ways to manipulate the image, however I can not find an example to fill in a pre-existing shape within the image. I see that using floodfill() is an option, although I'm not sure how to grab the shape I want to fill.

Note: I do not have access to any other image processing libraries for this task.

  • You might not have been searching with the term "flood-fill", which it sounds like you want to do, i.e. [How to flood-fill part of a bitmap enclosed by a black border with my chosen color?](https://stackoverflow.com/questions/10026346/how-to-flood-fill-part-of-a-bitmap-enclosed-by-a-black-border-with-my-chosen-col) – Nick T Sep 06 '17 at 20:42
  • Thanks for the suggestion Nick T. I had run across that page as well. The problem I am having with using that method is that I can not figure out to grab the shape I'm trying to fill. floodfill(img, (x,y), value, border) takes 4 parameters, I'm having trouble with the (x,y) as I don't want a specific pixel but rather the entire shape. Thanks for any suggestions. – Thomas Eggenberger Sep 06 '17 at 23:04
  • I would restate the question then as "how do I find a point inside a shape" or something to that effect. – Nick T Sep 06 '17 at 23:34
  • 1
    You can find the shape by finding contours. In your image you would have one contour. After doing so, you can find the centroid of the contour and use that point to perform floodfill operation. – Jeru Luke Sep 09 '17 at 10:06

1 Answers1

1

There are several ways of doing this. You could do as I do here, and fill all the areas outside the outline with magenta, then make everything that is not magenta into black, and then revert all artificially magenta-coloured pixels to white.

I have interspersed intermediate images in the code, but you can just grab all the bits of code and collect them together in order to have a working lump of code.

#!/usr/bin/env python3

from PIL import Image, ImageDraw
import numpy as np

# Open the image
im = Image.open('octagon.png').convert('RGB')

# Make all background (exterior to octagon) pixels magenta (255,0,255)
ImageDraw.floodfill(im,xy=(0,0),value=(255,0,255),thresh=200)
# DEBUG
im.save('intermediate.png')

enter image description here

# Make everything not magenta black
n  = np.array(im)
n[(n[:, :, 0:3] != [255,0,255]).any(2)] = [0,0,0]

# Revert all artifically filled magenta pixels to white
n[(n[:, :, 0:3] == [255,0,255]).all(2)] = [255,255,255]

Image.fromarray(n).save('result.png')

enter image description here

Or, you could fill all the background with magenta, then locate a white pixel and flood-fill with black using that white pixel as a seed. The method you choose depends on the expected colours of your images, and the degree to which you wish to preserve anti-aliasing and so on.

Mark Setchell
  • 191,897
  • 31
  • 273
  • 432