1

我有一个BaseSurf带有索引调色板的 Surface,我想给blit它另一个索引颜色的 Surface(我们称之为它NewSurf)。但是,BaseSurf 的调色板与 NewSurf 的不同,pygame 不会自动将缺少的颜色添加到 BaseSurf 的调色板中。因此,我需要一些方法将 NewSurf 的颜色附加到 BaseSurf 的调色板,而不会破坏任何一个(覆盖 Surface 像素实际使用的调色板索引)。

当我运行此代码时:

screen = pygame.display.set_mode((222,222))
screen.fill((255,255,255))

BaseSurf = pygame.load.image("4samps.png").convert(8)  # indexed with four colours; larger dimensions
NewSurf  = pygame.load.image("another4samps.png").convert(8)  # indexed with four more colours

screen.blit(BaseSurf, (10,10))
screen.blit(NewSurf, (160,43))

BaseSurf.blit(NewSurf, (33,33))

screen.blit(BaseSurf, (50,122))

……这就是我看到的……

不是我想的那样。

--编辑:请注意,组合图形将 NewSurf 中的颜色近似为 BaseSurf 使用的任何颜色,即使序列已关闭。右下角的 blob 实际上采用一个空值 (0,0,0,255) 作为最接近的匹配颜色!

显然,NewSurf(右侧)已将其索引转移到 BaseSurf,这并不完全不正确,但也不是我想要的。如何将 NewSurf 传送到 BaseSurf,在修改后的图像中保留其准确的颜色数据(但保留索引的彩色图像)?

这是另一个(未回答的)问题。

4

1 回答 1

2

我实际上在问它的过程中想出了一个答案。

使用pygame.Surface.set_palette_at(),我能够从 NewSurf 中提取调色板数据(使用NewSurf.get_palette(..)和迭代器来清除该调色板的空白)并将其粘贴到 BaseSurf 调色板的“结束”(即在最后一个非空白 RGBA 索引值之后)使用BaseSurf.set_palette_at(first_null_RGBA_index, new_RGBA_value).

您可以first_null_RGBA_value通过多种方式找到;我next()曾经blankRGBAget_palette(). 您还可以使用get_palette().index(blankRGBA)来获取第一个空白值的索引(如果您实际上在图形中有空白值像素,这是非常危险的!)。稍后我将描述这些方法及其问题(如我所见)。

碰巧的是,我似乎不需要重新索引 NewSurf,因为它的索引与它们在 BaseSurf 调色板中的新位置对齐。我不知道如果你重新映射一个重复的 RGB 值会有什么后果!我想 pygame 从 blit'd 图像中近似 RGB 值以匹配接收图像中最接近的值 - 如果您事先有意将整个调色板从 NewSurf 粘贴到 BaseSurf 中,则完全匹配。

我是这样做的。

   ... # initialized and blitted samples as before...
blankRGBA = (0,0,0,255)  # the RGBA value for null values in the index.

destpal   = list(BaseSurf.get_palette())
pallength = len(destpal) - 1 # This is probably always going to be 255, but just to be sure... (minus one 'cuz indices start at zero.)

nextblank = pallength - next((n for n, RGBA in enumerate(destpal[::-1]) if RGBA != blankRGBA), - 1)   
    #  ^ The palette will have a train of null values matching blankRGBA for every unusued index value. If the palette is complete full, it'll raise an error later.
    # This finds the index of the first such value by following the train until it 'starts', then subtracting that index number from the total length (probably 256). I'll explain why destpal.index(blankRGBA) is chancey later...

copypal = list(NewSurf.get_palette())
while True:
    if copypal[-1] == blankRGBA:  # Get rid of NewSurf's null index train, too.
        copypal.pop()
    else:
        print "Popped all trailing blank values from the incoming palette. %d entries remain." % len(copypal)
        break

    if not copypal:
        raise IndexError, "Depleted incoming palette. It was entirely blank entries?! What??"

    # Now that we have the useful section of NewSurf's palette (copypal) and the indices we can replace with it (nextblank), let's apply the new values and reindex NewSurf ahead of blitting it...

for n, newRGBA in enumerate(copypal):  
    if (nextblank + n) > 255: # It's possible the thing will fill up. For now, I'll have it throw an error.
        raise IndexError, "Ran out of palette space at %s! (colour number %d)" % (newRGBA, n)
    BaseSurf.set_palette_at((nextblank + n), newRGBA)  # Add the palette value to BaseSurf. As it happens, blit will reindex the colours on its own.

baseimage.blit(newimage, (33,33))
screen.blit(baseimage, (50, 122))

pygame.display.flip()

成功!

我可能还可以用类似的东西copypal = list(RGBA for RGBA in NewSurf.get_palette() if RGBA != blankRGBA)而不是while True; ... copypal.pop()循环来清除 NewSurf 调色板中的空白 RGBA 值。我也可以在 BaseSurf 的调色板中找到第一个空白,destpal.index(blankRGBA)而不是使用更复杂的next()指令。我没有做这两个的原因是因为调色板有可能使用图像中至少一个像素blankRGBA的值,并且这些像素本来是空白的 - 不太可能(0,0,0,255 ) 将在图像的某处使用。

据推测,如果是这种情况,RGBA 索引将位于调色板的开头而不是结尾。如果它们在除最后一个索引之外的任何地方,它们将是安全的。否则,可能会出现问题。


对于您可以非常密切地控制图像数据的情况,这些压缩版本也可以工作。

但是请注意,它们很可能不那么 Pythonic,因为它们更难阅读,并且它们更容易受到某些问题(意外替换实际正在使用的看起来为空的索引值)和未处理的异常(在基础冲浪)。

使用风险自负!

  # init as before...
for newRGBA in tuple(RGBA for RGBA in NewSurf.get_palette() if RGBA != blankRGBA):
    try: swapidx = next(n for n, RGBA in enumerate(BaseSurf.get_palette()) if RGBA == blankRGBA)
    except StopIteration: raise IndexError, "Ran out of palette space!"
    BaseSurf.set_palette_at(swapidx, newRGBA)

BaseSurf.blit(NewSurf, (33,33))
screen.blit(BaseSurf, (50, 122))

这有点慢,因为next()将遍历调色板中 copypal 中的每个值,但是嘿,它的行数更少,这是一件好事

你甚至可以只使用这个可怕且极其非 Pythonic 的单例:

[BaseSurf.set_palette_at(swapidx, newRGBA) for swapidx, newRGBA in zip(tuple(n for n, RGBA in enumerate(BaseSurf.get_palette()) if RGBA == blankRGBA), tuple(RGBA for RGBA in NewSurf.get_palette() if RGBA != blankRGBA))]  # PLEASE GOD NO.

请注意,我推荐任何一个短版本。我只是出于学术考虑将它们包括在内。

于 2014-12-14T06:22:54.067 回答