如何在 Pygame 表面中实现洪水填充

Posted

技术标签:

【中文标题】如何在 Pygame 表面中实现洪水填充【英文标题】:How to implement flood fill in a Pygame surface 【发布时间】:2017-01-15 01:04:07 【问题描述】:

我想知道一种填充 Pygame 表面的好方法。 我想要的最好的例子是油漆桶在 MS Paint 中的工作方式。

例如,如果在白色表面上画了一个黑色圆圈,我想在圆圈内填充白色(或任何形状)。

为了让您了解我在做什么,我正在制作一个像素艺术工具,并且我正在开发一个类似于 MS Paint 中的桶的功能。 (观看:http://imgur.com/a/ogtPV)

我尝试使用Surface.get_at()Surface.set_at() 来填充一堆,但是一旦你获得大约 100x100 像素的区域来填充,它就会滞后太多。

我也愿意接受任何其他不滞后的方法。

【问题讨论】:

@SimeonAleksov 不,这只会使整个表面成为我选择的任何颜色。如果您不明白我在做什么,请进入 MS Paint 并弄乱油漆桶。 我想位图编辑器中的那个工具叫做“flood fill”,如果你搜索算法可能会很有用。 附带说明,如果您对像素艺术工具很认真,我建议您将 Numpy + OpenCV 用于所有逻辑,并将 Pygame 仅用作前端(最终数组表示和鼠标/键盘输入) @MikhailV Pygame 可以正常工作。 :P 【参考方案1】:

我找到了一种方法,它大致将60 ms 用于100x100 区域,在2000 ms 下用于1000x1000 区域。代码中的解释。

import random
import pygame
pygame.init()

screen = pygame.display.set_mode((1024, 640))
clock = pygame.time.Clock()

image = pygame.image.load('delete_image.png').convert()


def fill(surface, position, fill_color):
    fill_color = surface.map_rgb(fill_color)  # Convert the color to mapped integer value.
    surf_array = pygame.surfarray.pixels2d(surface)  # Create an array from the surface.
    current_color = surf_array[position]  # Get the mapped integer color value.

    # 'frontier' is a list where we put the pixels that's we haven't checked. Imagine that we first check one pixel and 
    # then expand like rings on the water. 'frontier' are the pixels on the edge of the pool of pixels we have checked.
    #
    # During each loop we get the position of a pixel. If that pixel contains the same color as the ones we've checked
    # we paint it with our 'fill_color' and put all its neighbours into the 'frontier' list. If not, we check the next
    # one in our list, until it's empty.

    frontier = [position]
    while len(frontier) > 0:
        x, y = frontier.pop()
        try:  # Add a try-except block in case the position is outside the surface.
            if surf_array[x, y] != current_color:
                continue
        except IndexError:
            continue
        surf_array[x, y] = fill_color
        # Then we append the neighbours of the pixel in the current position to our 'frontier' list.
        frontier.append((x + 1, y))  # Right.
        frontier.append((x - 1, y))  # Left.
        frontier.append((x, y + 1))  # Down.
        frontier.append((x, y - 1))  # Up.

    pygame.surfarray.blit_array(surface, surf_array)


while True:
    clock.tick(30)

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            quit()
        elif event.type == pygame.MOUSEBUTTONDOWN:
            if event.button == 1:
                color = random.choice(tuple(pygame.color.THECOLORS.values()))
                print('Running')
                time = pygame.time.get_ticks()
                fill(image, event.pos, color)
                print('Finished in  ms'.format(pygame.time.get_ticks() - time))

    screen.blit(image, (0, 0))
    pygame.display.update()

这是我尝试过的图片(如果您尝试出售图片,我将收取收入):

【讨论】:

我不卖。我可能会重新分配它。 :P @LarryMcMuffin 没问题,只要不用于商业用途;) @LarryMcMuffin 我编辑我的答案。这种方法应该快两倍以上! @LarryMcMuffin 哦,对了,你需要安装numpy。最简单的是通过 pip 安装。 我使用的是 python 3.3.3,所以我很难使用 pip。 xD【参考方案2】:

这种算法称为“洪水填充”。 Pygame 没有内置的洪水填充功能。最简单的选择可能是使用 OpenCV 库。谷歌为您的平台“安装 opencv”。

然后你可以将它包装在 Pygame 表面的函数中:

def Fill(Surf, Point, Color):
    arr = pygame.surfarray.array3d(Surf)    # copy array from surface
    swapPoint = (Point[1], Point[0])        # swap X and Y
    cv2.floodFill(arr, None, swapPoint, Color)
    pygame.surfarray.blit_array(Surf, arr)

这将用颜色填充一个点处的连接区域。

【讨论】:

以上是关于如何在 Pygame 表面中实现洪水填充的主要内容,如果未能解决你的问题,请参考以下文章

在 Keras 中实现模型。如何解释填充/步幅值?

如何在 pygame 表面打洞? [复制]

如何在 Pygame 中将具有一定透明度的 PNG 粘贴到表面上? [复制]

如何在一个表面上为 pygame 提供多个输入框并将这些输入存储在变量中? [复制]

如何最佳解决洪水填充难题?

如何在 tableView 单元格中实现收藏按钮?