如何在Pygame中更改图像的颜色而不改变其透明度?

6
似乎这是一件简单的事情,但我还在努力。 我有一个名为“zero”的带有透明中心的矩形png图像。我想改变图像可见部分的颜色。为了尝试更改它,我尝试使用“zero.fill”,但我尝试的每个选项都不能仅更改非透明部分,或者将新颜色与旧颜色合并并保持不变。 我不想安装numpy,因为我希望将完成的程序交给没有它的朋友。欢迎任何简单的建议。

1
将图像放置在相同大小的矩形上,使用所需的颜色。 - Ankur Jyoti Phukan
我尝试过了,但是要么透明度不起作用,要么它会穿透到有色矩形图像下面的黑色背景。 - John_F
1
你需要在代码中能够更改颜色吗?如果不需要,你可以使用任何图像软件创建另一张图片,并根据某些条件在代码中在两者之间进行切换。 - The4thIceman
1
我根据Ankur的反转建议将其剥离并重建,结果它成功了。我仍然不知道为什么第二次可以工作而第一次不行?就我所知,两个代码是一样的。 - John_F
1
确实很奇怪,你在之前和之后都发布了代码。我现在很好奇。 - The4thIceman
你应该把你的代码发出来,以便帮助其他遇到相同问题的人。 - skrx
5个回答

6

我有一个例子,可以处理每个像素的透明度,还可以半透明显示。 fill 函数只是循环遍历表面上的像素,并将它们设置为新的颜色,但保留它们的 alpha 值。建议不要在每个帧与许多表面上执行此操作。(按f、g、h键可更改颜色。)

import sys
import pygame as pg


def fill(surface, color):
    """Fill all pixels of the surface with color, preserve transparency."""
    w, h = surface.get_size()
    r, g, b, _ = color
    for x in range(w):
        for y in range(h):
            a = surface.get_at((x, y))[3]
            surface.set_at((x, y), pg.Color(r, g, b, a))


def main():
    screen = pg.display.set_mode((640, 480))
    clock = pg.time.Clock()

    # Uncomment this for a non-translucent surface.
    # surface = pg.Surface((100, 150), pg.SRCALPHA)
    # pg.draw.circle(surface, pg.Color(40, 240, 120), (50, 50), 50)
    surface = pg.image.load('bullet2.png').convert_alpha()
    surface = pg.transform.rotozoom(surface, 0, 2)

    done = False

    while not done:
        for event in pg.event.get():
            if event.type == pg.QUIT:
                done = True
            if event.type == pg.KEYDOWN:
                if event.key == pg.K_f:
                    fill(surface, pg.Color(240, 200, 40))
                if event.key == pg.K_g:
                    fill(surface, pg.Color(250, 10, 40))
                if event.key == pg.K_h:
                    fill(surface, pg.Color(40, 240, 120))

        screen.fill(pg.Color('lightskyblue4'))
        pg.draw.rect(screen, pg.Color(40, 50, 50), (210, 210, 50, 90))
        screen.blit(surface, (200, 200))

        pg.display.flip()
        clock.tick(30)


if __name__ == '__main__':
    pg.init()
    main()
    pg.quit()
    sys.exit()

bullet2.png


我在我的答案中添加了一个半透明的图像,这样你就可以更好地看到效果。只有颜色改变了,但透明度保持不变。 - skrx
1
谢谢skrx,这对于复杂或主题图像将更有用。 - John_F
1
不久之后,我就需要使用skrx的方法了。它更加灵活。再次感谢。:-) - John_F

1
在这个版本中,根据Ankur的建议,可见部分和透明部分与原始问题相反。 以下是基本工作代码:
import pygame, sys
from pygame.locals import *
pygame.init()

def load_image(name):
    image = pygame.image.load(name).convert()
    return image

def resize(obj, w, h):
    global scale
    return pygame.transform.scale(obj, (int(w * scale), int(h * scale)))


pink = (255, 0, 160)
red = (255, 0, 0)
peach = (255, 118, 95)
blue = (0, 0, 255)
blue_1 = (38, 0, 160)
dark_yellow = (255, 174, 0)
green = (38, 137, 0)
orange = (255, 81, 0)
colour = [pink, red, peach, blue, blue_1, dark_yellow, green, orange, green]
clock = pygame.time.Clock()
scale = 4
screen = pygame.display.set_mode((292 * scale, 240 * scale),0, 32)
banner = load_image("banner.png") #292x35
zero = load_image("zero.png") #5x7
c = 0
while True:
    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            sys.exit()
    rgb = colour[c]
    c = c + 1
    if c > 7:
        c = 0
    pygame.draw.line(banner, rgb, (53, 21), (53, 27), 5) #line the same size as zero image
    banner.blit(zero, (51, 21)) #blit image with transparency over line
    large_banner = resize(banner, 292, 35)
    screen.blit(large_banner, (0, 0))
    clock.tick(120)
    pygame.display.flip()

pygame.quit()
sys.exit()

啊,好的,那只是改变了下面矩形(或者在这种情况下是线)的颜色,然后使用透明度将图像贴在上面。很高兴这对你有用,但实际上这并没有回答问题。 - skrx

0

这段代码用于降低所有颜色的亮度

    def drop(surface):
        w, h = surface.get_size()
        for x in range(w):
            for y in range(h):
                r = surface.get_at((x, y))[0]
                if r>150:
                   r-=50
                g = surface.get_at((x, y))[1]
                if g>150:
                   g-=50
                b = surface.get_at((x, y))[2]
                if b>150:
                   b-=50
                a = surface.get_at((x, y))[3]
                surface.set_at((x, y), pygame.Color(r, g, b, a)) 

    img = pygame.image.load("image.png").convert_alpha()
    drop(img)

0

我解决这个问题的方法与其他人似乎有些不同,但我一直在尝试弄清楚这个问题,这里列出的答案并没有给我我想要的东西。我慢慢地根据我找到的东西来实现我的目标。

首先,我只是想将我的动画帧的颜色更改为从列表中选择的随机颜色。我发现的方法使用了pygame.PixelArray()和.replace()来交换一种颜色为另一种颜色:

self.fire_frame_list = glob.glob("pictures\\Boom_Pow\\Colour_Flame\\*.png")
self.fire_frame_list.sort()
self.fire_frame_anim = [pygame.PixelArray(pygame.image.load(path)) for path in self.fire_frame_list]
...
self.fire_img = self.fire_frame_anim[self.frame_index]
self.fire_img.replace(self.colour, self.new_colour)
...
self.colour = self.new_colour
self.new_colour = randRGB()

在这段代码中,我收集了我的动画帧的路径,对它们进行排序,然后加载并转换图像为PixelArays。我使用一个名为frame_index的变量来选择当前的图像,然后更改该帧的颜色。然而,当我决定在动画中途更改颜色时,遇到了一个问题;我的动画的后半部分 - 在我更改所需颜色之后出现的帧 - 仍然是它们原来的颜色 - 白色。

为了解决这个问题,我必须确保所有的动画帧在创建时都更改了它们的颜色。

self.fire_frame_list = glob.glob("pictures\\Boom_Pow\\Colour_Flame\\*.png")
self.fire_frame_list.sort()
self.fire_frame_anim = [pygame.PixelArray(pygame.image.load(path)) for path in self.fire_frame_list]
for img in self.fire_frame_anim:
    img.replace((255,255,255), self.new_colour)

一段时间以来,这段代码一直运行得很完美,直到我尝试使用半透明图像。我确保在我的图像上使用了.convert_alpha(),以保持我的基础颜色为白色,并使alpha值成为更改后的值。这个方法是有效的,但是对于我的PixelArrays却不起作用。

虽然打印出每个RGBA值列出的像素列表显示了正确的值,但.replace()只会更改完全不透明的像素的颜色,其余部分仍然是白色。经过多次实验,我意识到需要使用与检查.convert_alpha()是否给出所需值时相同的方法创建我的像素alpha值列表。

self.img = pygame.image.load("pictures\\Boom_Pow\\Bubbloid.png").convert_alpha()
self.pix = {self.img.get_at((i, j))[3] for j in range(10) for i in range(10)}
self.img = pygame.PixelArray(self.img)

for a in self.pix:
    self.img.replace((255, 255, 255, a), self.colour + (a,))

首先,我加载我的图像并将其转换为alpha值。接下来,我创建了一个字典,其中每个键都是可能的像素alpha值 - [3]是颜色列表中alpha值的索引号。使用字典键可以确保没有重复项。然后,我将我的图像转换为PixelArrays,并运行一个循环,为每个可能的alpha值更改颜色。
这种方法已经按照我的愿望工作了,但我认为重要的是指出,我只有4个透明度级别,我不知道Python如何处理数十甚至数百个透明度级别。但是,对于我创建的简单图像,这种方法有效,并且我希望能够帮助其他人。 许多我的小气泡,填充着随机颜色 我的灯笼,在动画进行到一半时改变颜色

0

这比所有答案描述的都要简单得多。

如果你阅读Surface.fill文档,它说你可以使用special_flags来改变混合模式。这是自pygame 1.8发布于2008年以来的情况。

这是我从Open Game Art获得的一个小图形:

Original monster image

如果你尝试不同的special_flags,你可以创建不同的效果。MIN函数可能是你想要的。特别是当你的基础图像是白色/鲜艳色彩时。
def some_loading_function() -> pygame.Surface:
    # just pretend that we're loading an image here

graphic = some_loading_function().convert_alpha()
red = (255, 0, 0, 255)
graphic.fill(red, special_flags=pygame.BLEND_RGBA_MIN)

Monster with red overlay applied

MIN函数将填充颜色与原始像素颜色进行比较,并保留每个通道的最小值。使用上面的示例:

  • 红色始终是最大值,因此选择原始值
  • 蓝色始终是最小值,因此舍弃原始值
  • 绿色始终是最小值,因此舍弃原始值
  • Alpha始终是最大值,因此选择原始值

如果您想将整个可见区域设置为相同的颜色,则首先使用MAX函数(基本上是MIN的相反)将每个可见像素替换为白色,然后使用MIN填充所需的颜色:

graphic.fill((255, 255, 255, 0), special_flags=pygame.BLEND_RGBA_MAX)
graphic.fill((0, 200, 100, 255), special_flags=pygame.BLEND_RGBA_MIN)

Monster texture replaced with a block colour


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接