在pygame中的透明表面上渲染抗锯齿文本

Posted

技术标签:

【中文标题】在pygame中的透明表面上渲染抗锯齿文本【英文标题】:Render anti-aliased text on transparent surface in pygame 【发布时间】:2013-03-07 11:11:42 【问题描述】:

我正在制作一个函数,它接受一个字符串 a 将其分成几行并返回一个表面,每一行都呈现在前一行之下。

例如:

Line1\nLine 2

渲染到:

Line1
Line2

无论如何,我的问题是我无法将正确透明的表面返回给调用函数。我试过使用颜色键,但它不适用于抗锯齿文本。有没有办法我可以做到这一点?感谢您的回答。

我的代码:(目前在文本周围留下难看的紫色阴影)

def render_message(messages):
    surfaces = []
    height = 0
    max_width = 0

    for message in messages.split('\n'):
        surf = FONT.render(message, True, (0, 0, 0))
        surfaces.append(surf)
        height += surf.get_height() + 5
        if surf.get_width() > max_width:
            max_width = surf.get_width()

    result = pygame.Surface((max_width, height))
    result.fill((255, 0, 255))
    result.set_colorkey((255, 0, 255))

    top = 0
    for surface in surfaces:
        result.blit(surface, (max_width/2-surface.get_width()/2, top))
        top += surface.get_height() + 5

    return result

【问题讨论】:

【参考方案1】:

搞定了。主要不是我发现的是:no AA = 自动透明。使用AA你也需要设置颜色键

这是一个工作示例,它会切换 BG 以确保它是透明的。

import pygame
from pygame import Surface
#from pygame.locals import Rect, Color
from pygame.locals import *

class TextWall():
    def __init__(self, font=None, size=300):
        # for simplicity uses one font, one size for now.
        # You also can make font size, .text, etc be properties so they *automatically* toggle dirty bool.
        self.font_name = font
        self.font_size = size
        self.color_fg = Color("white")
        self.color_bg = Color("gray20")
        self.aa = True 
        self.text = "hi world"

        self.dirty = True
        self.font = pygame.font.Font(font, size)
        self.screen = pygame.display.get_surface()


    def _render(self):
        # re-render
        """no AA = automatic transparent. With AA you need to set the color key too"""
        self.dirty = False        
        self.text1 = self.font.render(self.text, self.aa, self.color_fg)            
        self.rect1 = self.text1.get_rect()

    def draw(self):
        # blits use cached surface, until text change makes it dirty
        if self.dirty or self.text1 is None: self._render()
        self.screen.blit(self.text1, self.rect1)

    def text(self, text):
        self.dirty = True
        self.text_message = text # parse

class Game():
    done = False
    def __init__(self):
        pygame.init()
        self.screen = pygame.display.set_mode ((640,480))
        self.text = Surface([200,100])

        self.text_wall = TextWall()
        self.toggle_bg = True
    def loop(self):
        while not self.done:
            self.handle_events()
            self.draw()

    def draw(self):
        if self.toggle_bg: bg = Color("darkred")
        else: bg = Color("gray20")

        self.screen.fill(bg)        
        self.text_wall.draw()        
        pygame.display.update()

    def handle_events(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT: self.done = True

            elif event.type == KEYDOWN:
                if event.key == K_ESCAPE: self.done = True                
                elif event.key == K_SPACE: self.toggle_bg = not self.toggle_bg
                elif event.key == K_a: 
                    self.text_wall.aa = not self.text_wall.aa
                    self.text_wall.dirty = True


if __name__ == "__main__":
    g = Game()
    g.loop()

编辑:改进的代码,使用 alpha 通道而不是 set_colorkey。

【讨论】:

很抱歉,但在我的机器上,即使使用您的代码,问题仍然存在。我下载并运行了你的代码,但我仍然看到字母周围是灰色的。我更改了字体颜色、背景颜色以及我能想到的几乎所有内容,但它拒绝透明。 我的代码没有做任何改动?我刚刚再次测试了我的,它起作用了。 -- 你的显示器上是否有任何标志或每像素设置的特定位? 是的。我逐字运行您的代码,边缘有灰色。不是很多,(可能是因为系统字体非常线性)但仍然存在。将背景和颜色键设置为绿色使其更加明显。我不知道标志,据我所知,一切都设置为默认值。 它必须为某事起别名。我选择了灰色作为我的背景,所以当你将屏幕切换到红色时,文字周围会有一点灰色。所以选择你所期望的前景和背景颜色。 但是我的背景是一个带有复杂图案的图像。我想渲染文本。这不可能吗?【参考方案2】:

发布了第二个答案,因为这更复杂,人们可能希望同时查看。

1 , 2 减小/增大字体大小 space切换背景

我把文本换行了。编辑 parse_text() 以创建您想要的文本列表。

import pygame
from pygame import Surface
from pygame.locals import *
# Todo: remove font object from TextLine() , to TextWall(). Then share a list of font's with any line.

"""Example of multi-line text class, with alpha transparency."""
lorem = """Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed aliquet
tellus eros, eu faucibus dui. Phasellus eleifend, massa id ornare sodales, est urna
congue tellus, vitae varius metus nunc non enim. Mauris elementum, arcu vitae tempor euismod, justo turpis malesuada est, sed dictum nunc nulla nec mauris. Cras felis eros, elementum vitae sollicitudin in, elementum et augue. Proin eget nunc at dui congue pretium. Donec ut ipsum ut lacus mollis tristique. In pretium varius dui eu dictum.

Proin pulvinar metus nec mi semper semper. Pellentesque habitant morbi tristique
senectus et netus et malesuada fames ac turpis egestas. Proin in diam odio. Vestibulum
at neque sed ante sodales eleifend quis id dui. Mauris sollicitudin, metus a semper consectetur,
est lectus varius erat, sit amet ultrices tortor nisi id justo. Aliquam elementum vestibulum dui ut auctor. Mauris commodo sapien vitae augue tempus sagittis. Morbi a nibh lectus, sed porta nibh. Donec et est ac dui sodales aliquet tristique et arcu. Nullam enim felis, posuere vel rutrum eu, euismod a purus. Morbi porta cursus libero, id rutrum elit lacinia vitae.

In condimentum ultrices ipsum, ut convallis odio egestas et. Cras at egestas elit. Morbi
quis neque ligula. Sed tempor, sem at fringilla rhoncus, diam quam mollis nisi, vitae semper
mi massa sit amet tellus. Vivamus congue commodo ornare. Morbi et mi non sem malesuada rutrum. Etiam est purus, interdum ut placerat sit amet, tempus eget eros. Duis eget augue quis diam facilisis blandit. Ut vulputate adipiscing eleifend. """

class TextLine(object):
    # Manages drawing and caching a single line of text
    # You can make font size, .color_fg etc be properties so they *automatically* toggle dirty bool.
    def __init__(self, font=None, size=16, text="hi world"):        
        self.font_name = font
        self.font_size = size
        self.color_fg = Color("white")
        self.color_bg = Color("gray20")

        self._aa = True 
        self._text = text                
        self.font = pygame.font.Font(font, size)
        self.screen = pygame.display.get_surface()

        self.dirty = True
        self.image = None
        self._render()

    def _render(self):
        # render for cache
        """no AA = automatic transparent. With AA you need to set the color key too"""
        self.dirty = False        
        self.image = self.font.render(self._text, self.aa, self.color_fg)            
        self.rect = self.image.get_rect()

    def draw(self):
        # Call this do draw, always prefers to use cache
        if self.dirty or (self.image is None): self._render()
        self.screen.blit(self.image, self.rect)        

    @property
    def text(self):
        return self._text

    @text.setter
    def text(self, text):
        self.dirty = True
        self._text = text

    @property
    def aa(self): return self._aa

    @aa.setter
    def aa(self, aa):
        self.dirty = True
        self._aa = aa

class TextWall(object):
    # Manages multiple lines of text / paragraphs.
    def __init__(self, font=None, size=16):
        self.font = font
        self.font_size = size        
        self.offset = Rect(20,20,1,1) # offset of whole wall

        self.screen = pygame.display.get_surface()
        self.dirty = True
        self.text_lines = []
        self._text_paragraph = "Empty\nText"
        self._render()

    def _render(self):
        # render list 
        self.dirty = False
        self.text_lines = [ TextLine(self.font, self.font_size, line) for line in self._text_paragraph ]        

        # offset whole paragraph
        self.text_lines[0].rect.top = self.offset.top

        # offset the height of each line
        prev = Rect(0,0,0,0)        
        for t in self.text_lines:
            t.rect.top += prev.bottom
            t.rect.left = self.offset.left
            prev = t.rect

    def parse_text(self, text):
        # parse raw text to something usable
        self._text_paragraph = text.split("\n")
        self._render()

    def draw(self):
        # draw with cached surfaces    
        if self.dirty: self._render()
        for text in self.text_lines: text.draw()

    @property
    def font_size(self):
        return self._font_size

    @font_size.setter
    def font_size(self, size):
        self.dirty = True
        self._font_size = size

    @property
    def text(self):
        return self._text_paragraph

    @text.setter
    def text(self, text_paragraph):
        self.dirty = True
        self.parse_text(text_paragraph)

class Game():
    done = False
    def __init__(self):
        pygame.init()
        self.screen = pygame.display.set_mode ((640,480))
        self.text = Surface([200,100])

        self.text_wall = TextWall()
        self.toggle_bg = True

        self.text_wall.parse_text(lorem)

    def loop(self):
        while not self.done:
            self.handle_events()
            self.draw()

    def draw(self):
        if self.toggle_bg: bg = Color("gray60")
        else: bg = Color("gray20")

        self.screen.fill(bg)        
        self.text_wall.draw()        
        pygame.display.update()

    def handle_events(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT: self.done = True

            elif event.type == KEYDOWN:
                if event.key == K_ESCAPE: self.done = True                
                elif event.key == K_SPACE: self.toggle_bg = not self.toggle_bg
                elif event.key == K_1: self.text_wall.font_size -= 3
                elif event.key == K_2: self.text_wall.font_size += 3

if __name__ == "__main__":
    g = Game()
    g.loop()

【讨论】:

那么让我看看我是否理解你的回答。只需将每一行文本直接绘制到屏幕上,基本上就不需要透明表面了吗? 没有。它将文本渲染到具有 alpha 透明度的新表面。 (这是可变级别,颜色键仅使用单个级别)。当你想将它blit到屏幕上时,你可以blit这个表面。 请原谅我这么密集,但是使用这种可变透明度的 Surface 在哪里呢?我所看到的只是字体被直接传送到屏幕上。 self.image = self.font.render(self._text, self.aa, self.color_fg) 创建一个新图像。它使用 alpha 变量,因此每个像素将是 RGBA,例如:(100,100,100,50) 表示它们具有 0-255 范围内的 alpha 值,而不仅仅是 RGB 值。就像png 图像具有可变透明度一样。但是如果你set_colorkey,你只能得到一个像素颜色作为完全透明,就像gif 图像。 对。好的,我想我现在明白了。感谢您的耐心等待!【参考方案3】:

最好的解决方案是不使用颜色键,而是使用 Alpha 通道。而不是这个:

result = pygame.Surface((max_width, height))
result.fill((255, 0, 255))
result.set_colorkey((255, 0, 255))

试试这个:

result = pygame.Surface((max_width, height), pygame.SRCALPHA)

【讨论】:

以上是关于在pygame中的透明表面上渲染抗锯齿文本的主要内容,如果未能解决你的问题,请参考以下文章

在OpenGL中绘制到半透明帧缓冲区时如何抗锯齿? [复制]

如何将非抗锯齿字体渲染到内部图像?

画布中的抗锯齿大文本

◮OpenGL-抗锯齿

如何设置透明背景颜色并对 kivy 图像进行抗锯齿处理?

防止 TrueType 字体的抗锯齿(或子像素渲染)