Pygame 中的光晕效果使文本发光

Bloom Effect in Pygame so that text glows

我想复制 Watson-scott 测试中显示的效果,其中的文字似乎会发光。 Link 举例:https://www.youtube.com/watch?v=2ySNm4gltkE

跳到 11:17 文本似乎在发光的地方;我如何用 pygame 复制这种效果?我尝试在文本背景中添加一个灰色矩形,但它看起来很糟糕。我也试过像 this example 这样隐藏文本,但没有任何效果。

我也在使用 Python 3.7.4。感谢您的帮助,我真的很需要它!!

好吧,有时我们可以说这是不可能的,但通常这不是该软件包的主要目标。尽管如此,让我们看看能不能解决这个问题。

我冒昧地假设允许 pygame 之外的其他包,但最终结果应该在 pygame 中可见。为了创建绽放/发光效果,我使用包 opencv-python (cv2) 和 numpy (np).

解决方案的第一部分将讨论创建发光边框和一些发光文本。第二部分将讨论如何在 pygame 表面上进行渲染。

TL;DR;跳到下面的摘要部分并将代码复制到各自的文件中。

第 1 部分

花开

为了获得漂亮的发光边框和文字,我们可以使用opencv的blurring功能,也称为平滑。由于我们想要创建不同强度的发光,我们首先应用 GaussianBlur,在图像周围创建一些随机模糊度,然后使用正常 blur.

扩展该模糊度
def apply_blooming(image: np.ndarray) -> np.ndarray:
    # Provide some blurring to image, to create some bloom.
    cv2.GaussianBlur(image, ksize=(9, 9), sigmaX=10, sigmaY=10, dst=image)
    cv2.blur(image, ksize=(5, 5), dst=image)
    return image

注意:内核大小 (ksize) 和西格玛 (sigmaXsigmaY) 的值是根据经验选择的,您可以对这些值进行一些调整, 直到你得到你想要的。

颜色

一个小插曲,因为我们需要提供一些非常漂亮的可怕色彩,下面的 class 包含一些(可怕的)颜色。

class Colors:
    WHITE_ISH = (246, 246, 246)
    YELLOW_ISH = (214, 198, 136)
    RED_ISH = (156, 60, 60)

发光边框

为了获得发光的边框,制作了一个辅助函数,它将绘制一个具有一些预定义属性的矩形。所选属性为:

  • 边距:边框将被绘制到离图像边那么远的地方。
  • thickness:边框将由这么多像素组成。
  • color: 边框的颜色,方便更改。
def create_border(image: np.ndarray, margin: int, thickness: int, color: Colors) -> np.ndarray:
    """
    Create a normal border around an image, with specified colors.

    Args:
        image: The image, that requires a border.
        margin: The border distance from the sides of the image.
        thickness: The thickness of the border.
        color: The border color, by default a slightly yellow color.

    Modifies:
        The input image, will be modified with a border.

    Returns:
        The same image, with a border inserted.

    """

    # Numpy uses the convention `rows, columns`, instead of `x, y`.
    # Therefore height, has to be before width.
    height, width = image.shape[:2]
    cv2.rectangle(image, (margin, margin), (width - margin, height - margin), color, thickness=thickness)
    return image

然后可以使用 apply_bloomingcreate_border 函数绘制最终边框。

def glowing_border(image: np.ndarray, margin=20, thickness=10, color: Colors = Colors.WHITE_ISH):
    """

    Create a glowing border around an image.

    Args:
        image: The image, that requires a border.
        margin: The border distance from the sides of the image.
        thickness: The thickness of the border.
        color: The border color, by default a slightly yellow color.

    Modifies:
        The input image, will be modified with a blooming border.

    Returns:
        The same image, with a blooming border inserted.
    """

    # Generate yellowish colored box
    image = create_border(image, margin, thickness, color)

    # Apply the blooming.
    image = apply_blooming(image)

    # Reassert the original border, to get a clear outline.
    # Similar to the Watson-Scott test, two borders were added here.
    image = create_border(image, margin - 1, 1, color)
    image = create_border(image, margin + 1, 1, color)
    return image

测试代码

为了测试发光边框,我们可以使用cv2.imshow来显示图像。由于我们稍后要使用此功能,因此创建了一个小函数。此函数将图像和显示时间(代码继续执行之前的等待时间)作为输入。


def show(image, delay=0):
    """
    Display an image using cv2.

    Notes:
        By default cv2 uses the BGR coloring, instead RGB.
        Hence image shown by cv2, which are meant to be RGB,
        has to be transformed using `cvtColor`.

    Args:
        image: Input image to be displayed
        delay: Time delay before continuing running.
            When 0, The program will wait until a key stroke or window is closed.
            When 1, The program will continue as quickly as possible.

    Returns:
        Nothing, it displays the image.

    """
    cv2.imshow('Test', cv2.cvtColor(image, cv2.COLOR_RGB2BGR))
    cv2.waitKey(delay)

实际测试代码:

image = np.zeros((480, 640, 3), dtype=np.uint8)
border = glowing_border(image.copy(), color=Colors.YELLOW_ISH)
show(border, delay=0)

发光文字

通过使用 cv2.putText

可以对发光文本使用类似的方法
def glowing_text(image: np.ndarray, text: str, org: Tuple[int, int], color: Colors) -> np.ndarray:
    """

    Args:
        image: The image, that requires a border.
        text: The text to be placed on the image.
        org: The starting location of the text.
        color: The color of the text.


    Modifies:
        The input image, will be modified with a blooming text.

    Returns:
        The same image, with a blooming text inserted.
    """

    image = cv2.putText(image, text, org, cv2.FONT_HERSHEY_COMPLEX_SMALL, fontScale=.7, color=color, thickness=1)
    image = apply_blooming(image)
    image = cv2.putText(image, text, org, cv2.FONT_HERSHEY_COMPLEX_SMALL, fontScale=.7, color=color, thickness=1)
    return image

有测试码

image = np.zeros((480, 640, 3), dtype=np.uint8)
text = glowing_text(image.copy(), text="Welcome to this game", org=(50, 70), color=Colors.YELLOW_ISH)
show(text, delay=0)

间奏曲

在我继续展示如何在 pygame 中显示它之前,我会加一个奖励,展示文本如何出现在屏幕上,就像一个人在慢慢输入一样。以下代码之所以有效,是因为我们分别绘制边框和文本,然后使用 np.bitwise_or.

组合结果
image = np.zeros((480, 640, 3), dtype=np.uint8)

# Create the glowing border, and a copy of the image, for the text, that will be placed on it later.
border = glowing_border(image.copy(), color=Colors.YELLOW_ISH)
text = image.copy()

# This message will be incrementally written
message = "Welcome to this game. Don't be scared :)."

for idx in range(len(message) + 1):
    text = glowing_text(image.copy(), text=message[:idx], org=(50, 70), color=Colors.YELLOW_ISH)

    # We use a random time delay between keystrokes, to simulate a human.
    show(np.bitwise_or(border, text), delay=np.random.randint(1, 250))

# Pause the screen after the full message.
show(np.bitwise_or(border, text), delay=0)

注意:或者我们可以先在同一张图片上生成边框和文本,然后应用高光滤镜。请记住,我们必须再次重绘边框和文本,为它们提供坚实的基础。

第 2 部分

现在我们可以生成一个 canvas 带有正确的边框和文本,它必须被插入到 pygame 中。让我们将之前所有的函数放入一个名为 blooming.py 的文件中,并在新文件 game.py.

中引用它

以下代码是如何将 numpy 数组放入 pygame 的最小工作示例。

import contextlib
from typing import Tuple

# This suppresses the `Hello from pygame` message.
with contextlib.redirect_stdout(None):
    import pygame

import numpy as np
import blooming


def image_generator(size: Tuple[int, int], color: blooming.Colors):
    image = np.zeros((*size[::-1], 3), dtype=np.uint8)

    # Create the glowing border, and a copy of the image, for the text, that will be placed on it later.
    border = blooming.glowing_border(image.copy(), color=color)
    text = image.copy()

    # This message will be incrementally written
    message = "Welcome to this game. Don't be scared :)."

    for idx in range(len(message) + 1):
        text = blooming.glowing_text(image.copy(), text=message[:idx], org=(50, 70), color=color)
        yield np.bitwise_or(border, text)
    return np.bitwise_or(border, text)


if __name__ == '__main__':
    pygame.init()
    screen = pygame.display.set_mode((640, 480))
    clock = pygame.time.Clock()
    running = True

    while running:
        for image in image_generator(screen.get_size(), color=blooming.Colors.YELLOW_ISH):
            screen.fill((0, 0, 0))

            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    running = False

                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_ESCAPE:
                        running = False

            # This is where we insert the numpy array.
            # Because pygame and numpy use different coordinate systems,
            # the numpy image has to be flipped and rotated, before being blit.
            img = pygame.surfarray.make_surface(np.fliplr(np.rot90(image, k=-1)))
            screen.blit(img, (0, 0))

            pygame.display.flip()
            clock.tick(np.random.randint(10, 30))

    pygame.quit()

摘要(长话短说;博士;)

  • blooming.py
from typing import Tuple

import cv2
import numpy as np


class Colors:
    WHITE_ISH = (246, 246, 246)
    YELLOW_ISH = (214, 198, 136)
    RED_ISH = (156, 60, 60)


def create_border(image: np.ndarray, margin: int, thickness: int, color: Colors) -> np.ndarray:
    """
    Create a normal border around an image, with specified colors.

    Args:
        image: The image, that requires a border.
        margin: The border distance from the sides of the image.
        thickness: The thickness of the border.
        color: The border color, by default a slightly yellow color.

    Modifies:
        The input image, will be modified with a border.

    Returns:
        The same image, with a border inserted.

    """

    # Numpy uses the convention `rows, columns`, instead of `x, y`.
    # Therefore height, has to be before width.
    height, width = image.shape[:2]
    cv2.rectangle(image, (margin, margin), (width - margin, height - margin), color, thickness=thickness)
    return image


def apply_blooming(image: np.ndarray) -> np.ndarray:
    # Provide some blurring to image, to create some bloom.
    cv2.GaussianBlur(image, ksize=(9, 9), sigmaX=10, sigmaY=10, dst=image)
    cv2.blur(image, ksize=(5, 5), dst=image)
    return image


def glowing_border(image: np.ndarray, margin=20, thickness=10, color: Colors = Colors.WHITE_ISH):
    """

    Create a glowing border around an image.

    Args:
        image: The image, that requires a border.
        margin: The border distance from the sides of the image.
        thickness: The thickness of the border.
        color: The border color, by default a slightly yellow color.

    Modifies:
        The input image, will be modified with a blooming border.

    Returns:
        The same image, with a blooming border inserted.
    """

    # Generate yellowish colored box
    image = create_border(image, margin, thickness, color)

    # Apply the blooming.
    image = apply_blooming(image)

    # Reassert the original border, to get a clear outline.
    # Similar to the Watson-Scott test, two borders were added here.
    image = create_border(image, margin - 1, 1, color)
    image = create_border(image, margin + 1, 1, color)
    return image


def glowing_text(image: np.ndarray, text: str, org: Tuple[int, int], color: Colors) -> np.ndarray:
    """

    Args:
        image: The image, that requires a border.
        text: The text to be placed on the image.
        org: The starting location of the text.
        color: The color of the text.


    Modifies:
        The input image, will be modified with a blooming text.

    Returns:
        The same image, with a blooming text inserted.
    """

    image = cv2.putText(image, text, org, cv2.FONT_HERSHEY_COMPLEX_SMALL, fontScale=.7, color=color, thickness=1)
    image = apply_blooming(image)
    image = cv2.putText(image, text, org, cv2.FONT_HERSHEY_COMPLEX_SMALL, fontScale=.7, color=color, thickness=1)
    return image


def show(image, delay=0):
    """
    Display an image using cv2.

    Notes:
        By default cv2 uses the BGR coloring, instead RGB.
        Hence image shown by cv2, which are meant to be RGB,
        has to be transformed using `cvtColor`.

    Args:
        image: Input image to be displayed
        delay: Time delay before continuing running.
            When 0, The program will wait until a key stroke or window is closed.
            When 1, The program will continue as quickly as possible.

    Returns:
        Nothing, it displays the image.

    """
    cv2.imshow('Test', cv2.cvtColor(image, cv2.COLOR_RGB2BGR))
    cv2.waitKey(delay)


if __name__ == '__main__':
    image = np.zeros((480, 640, 3), dtype=np.uint8)

    # Create the glowing border, and a copy of the image, for the text, that will be placed on it later.
    border = glowing_border(image.copy(), color=Colors.YELLOW_ISH)
    text = image.copy()

    # This message will be incrementally written
    message = "Welcome to this game. Don't be scared :)." + " " * 10

    for idx in range(len(message) + 1):
        text = glowing_text(image.copy(), text=message[:idx], org=(50, 70), color=Colors.YELLOW_ISH)

        # We use a random time delay between keystrokes, to simulate a human.
        show(np.bitwise_or(border, text), delay=np.random.randint(1, 250))

    # Pause the screen after the full message.
    show(np.bitwise_or(border, text), delay=0)
  • game.py
import contextlib
from typing import Tuple

# This suppresses the `Hello from pygame` message.
with contextlib.redirect_stdout(None):
    import pygame

import numpy as np
import blooming


def image_generator(size: Tuple[int, int], color: blooming.Colors):
    image = np.zeros((*size[::-1], 3), dtype=np.uint8)

    # Create the glowing border, and a copy of the image, for the text, that will be placed on it later.
    border = blooming.glowing_border(image.copy(), color=color)
    text = image.copy()

    # This message will be incrementally written
    message = "Welcome to this game. Don't be scared :)." + " " * 10

    for idx in range(len(message) + 1):
        text = blooming.glowing_text(image.copy(), text=message[:idx], org=(50, 70), color=color)
        yield np.bitwise_or(border, text)
    return np.bitwise_or(border, text)


if __name__ == '__main__':
    pygame.init()
    screen = pygame.display.set_mode((640, 480))
    clock = pygame.time.Clock()
    running = True

    while running:
        for image in image_generator(screen.get_size(), color=blooming.Colors.YELLOW_ISH):
            screen.fill((0, 0, 0))

            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    running = False

                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_ESCAPE:
                        running = False

            # This is where we insert the numpy array.
            # Because pygame and numpy use different coordinate systems,
            # the numpy image has to be flipped and rotated, before being blit.
            img = pygame.surfarray.make_surface(np.fliplr(np.rot90(image, k=-1)))
            screen.blit(img, (0, 0))

            pygame.display.flip()
            clock.tick(np.random.randint(10, 30))

    pygame.quit()

结果

请注意,实物看起来比这张图片锐利得多。此外,调整文本的粗细和模糊滤镜的大小也会对结果产生很大影响。对于此图像,GaussianBlurksize 已增加到 (17, 17),并且 sigmaXsigmaY 均已设置为 100.