有没有办法合并为索引pygame.Surfaces的调色板?

时间:2014-12-14 06:22:54

标签: python image-processing pygame color-mapping indexed-image

我有一个带有索引调色板的Surface BaseSurf,我想要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))

......这就是我所看到的......

Not what I had in mind.

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

显然,NewSurf(在右边)已将其索引转移到BaseSurf,这不是不正确的,但也不是我想要的。 如何将NewSurf blit转换为BaseSurf,在修改后的图像中保留其精确的颜色数据(但保留索引的彩色图像)?

Here is another (unanswered) question in the same vein.

1 个答案:

答案 0 :(得分:1)

我在询问过程中确实找到了答案。

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

您可以通过多种方式找到first_null_RGBA_value;我已经使用next()来查找第一个空值(由blankRGBA定义,在我的经验中通常是(0,0,0,255))在列车中#{0}在get_palette()中使用正在使用的调色板之后的null(未使用)值。您还可以使用get_palette().index(blankRGBA)来获取第一个空白值的索引(如果您在图形中实际有空值像素,那么这是非常危险的!)。我将在稍后描述方法及其问题(如我所见)。

碰巧,我似乎似乎必须重新索引NewSurf它的索引与BaseSurf调色板中的新位置对齐。 我不知道如果重新映射重复的RGB值会产生什么后果!我想pygame会从blit< d; image中近似RGB值来匹配接收者中最接近的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()

Success!

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

据推测,如果是这种情况,RGBA索引将位于调色板的 start 而不是 end 。如果它们只是最后一个索引,那么它们就是安全的。否则,可能会出现问题。


对于可以非常密切地控制图像数据的情况,这些精简版本也可以使用。

但是请注意,它们很可能不像Pythonic,因为它们更难以阅读,并且它们更容易受到某些问题的影响(意外地替换了实际使用中看似无效的索引值)和未处理的异常(用完BaseSurf中的调色板空间)。

使用风险自负!

  # 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中每个值的调色板,但是嘿,它的线数更少,而且 good 事情。

你甚至可以使用这个可怕的,非常非战斗的单身人士:

[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.

请注意,我推荐短版本。我只是将它们包括在内以供学术考虑。