feature improvement: improved performance of ImageDraw.floodfill with Python built-in set() datatype.

This commit is contained in:
yo1995 2018-08-06 18:13:57 +08:00
parent 69918dcbef
commit 24388addb6

View File

@ -325,22 +325,23 @@ def getdraw(im=None, hints=None):
def floodfill(image, xy, value, border=None, thresh=0): def floodfill(image, xy, value, border=None, thresh=0):
""" """
(experimental) Fills a bounded region with a given color. (experimental) Fills a bounded region with a given color.
:param image: Target image. :param image: Target image.
:param xy: Seed position (a 2-item coordinate tuple). See :param xy: Seed position (a 2-item coordinate tuple). See
:ref:`coordinate-system`. :ref:`coordinate-system`.
:param value: Fill color. :param value: Fill color.
:param border: Optional border value. If given, the region consists of :param border: Optional border value. If given, the region consists of
pixels with a color different from the border color. If not given, pixels with a color different from the border color. If not given,
the region consists of pixels having the same color as the seed the region consists of pixels having the same color as the seed
pixel. pixel.
:param thresh: Optional threshold value which specifies a maximum :param thresh: Optional threshold value which specifies a maximum
tolerable difference of a pixel value from the 'background' in tolerable difference of a pixel value from the 'background' in
order for it to be replaced. Useful for filling regions of non- order for it to be replaced. Useful for filling regions of non-
homogeneous, but similar, colors. homogeneous, but similar, colors.
""" """
# based on an implementation by Eric S. Raymond # based on an implementation by Eric S. Raymond
# amended by yo1995 @20180806
pixel = image.load() pixel = image.load()
x, y = xy x, y = xy
try: try:
@ -350,12 +351,15 @@ def floodfill(image, xy, value, border=None, thresh=0):
pixel[x, y] = value pixel[x, y] = value
except (ValueError, IndexError): except (ValueError, IndexError):
return # seed point outside image return # seed point outside image
edge = [(x, y)] edge = {(x, y)}
full_edge = set() # use a set to record each unique pixel processed
if border is None: if border is None:
while edge: while edge:
newedge = [] new_edge = set()
for (x, y) in edge: for (x, y) in edge: # 4 adjacent method
for (s, t) in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)): for (s, t) in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)):
if (s, t) in full_edge:
continue # if already processed, skip
try: try:
p = pixel[s, t] p = pixel[s, t]
except IndexError: except IndexError:
@ -363,13 +367,17 @@ def floodfill(image, xy, value, border=None, thresh=0):
else: else:
if _color_diff(p, background) <= thresh: if _color_diff(p, background) <= thresh:
pixel[s, t] = value pixel[s, t] = value
newedge.append((s, t)) new_edge.add((s, t))
edge = newedge full_edge.add((s, t))
full_edge = edge # do not record useless pixels to reduce memory consumption
edge = new_edge
else: else:
while edge: while edge:
newedge = [] new_edge = set()
for (x, y) in edge: for (x, y) in edge:
for (s, t) in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)): for (s, t) in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)):
if (s, t) in full_edge:
continue
try: try:
p = pixel[s, t] p = pixel[s, t]
except IndexError: except IndexError:
@ -377,7 +385,9 @@ def floodfill(image, xy, value, border=None, thresh=0):
else: else:
if p != value and p != border: if p != value and p != border:
pixel[s, t] = value pixel[s, t] = value
newedge.append((s, t)) new_edge.add((s, t))
full_edge.add((s, t))
full_edge = edge
edge = newedge edge = newedge