用sdl2绘制轮廓圆

时间:2017-03-07 18:07:09

标签: python graphics transparency sdl-2 pysdl2

TL; DR:使用(py)sdl2,我试图绘制一个圆形轮廓,其内部是透明的,从而显示在其后面绘制的对象。基本上我需要找到一种方法来擦除实心圆的内部像素(或者将它们绘制成/将它们设置回透明像素)。我想尽可能使用纹理而不是表面。

我正在努力实现看似概念上非常简单的事情,但我无法实现。

我想在python中用sdl2绘制一个圆形轮廓。当然,使用sdl2gfx函数circleRGBA()实现这一点非常简单,但它只允许您绘制线宽为1px的轮廓。用较粗的轮廓绘制圆圈似乎是不可能的。

我之前使用透明颜色键(inspired by this method)使用pygame表面做了类似的事情,我知道SDL2中也有表面,但警告是我&#39 ;我试图坚持更快的纹理,这不提供颜色键机制。

在视觉上更好地解释一下:我想要实现的目标是:

The goal

一个大于1px宽的圆(环),其内部是透明的,这样在圆圈后面绘制的物品将在内部可见。

我过去使用的一个技巧是绘制2个实心圆,其中第二个较小的圆与背景颜色相同。但是,这会遮挡圆圈后面的所有内容(在本例中为白线)。以下是使用此方法的函数示例:

@to_texture
def draw_circle(self, x, y, r, color, opacity=1.0, fill=True, aa=False, penwidth=1):
    # Make sure all spatial parameters are ints
    x, y, r = int(x), int(y), int(r)
    # convert color parameter to sdl2 understandable values
    color = sdl2.ext.convert_to_color(color)
    # convert possible opacity values to the right scale
    opacity = self.opacity(opacity)
    # Get background color set for this texture
    bgcolor = self.__bgcolor

    # Check for invalid penwidth values, and make sure the value is an int
    if penwidth != 1:
        if penwidth < 1:
            raise ValueError("Penwidth cannot be smaller than 1")
        if penwidth > 1:
            penwidth = int(penwidth)

    # if the circle needs to be filled, it's easy
    if fill:
        sdlgfx.filledCircleRGBA(self.sdl_renderer, x, y, r, color.r, color.g, color.b, opacity)
    else:
        # If penwidth is 1, simple use sdl2gfx own functions
        if penwidth == 1:
            if aa:
                sdlgfx.aacircleRGBA(self.sdl_renderer, x, y, r, color.r, color.g, color.b, opacity)
            else:
                sdlgfx.circleRGBA(self.sdl_renderer, x, y, r, color.r, color.g, color.b, opacity)
        else:
            # If the penwidth is larger than 1, things become a bit more complex.
            outer_r = int(r+penwidth*.5)
            inner_r = int(r-penwidth*.5)

            # Draw outer circle
            sdlgfx.filledCircleRGBA(self.sdl_renderer, x, y,
                outer_r, color.r, color.g, color.b, opacity)

            # Draw inner circle
            sdlgfx.filledCircleRGBA(self.sdl_renderer, x, y,
                inner_r, bgcolor.r, bgcolor.g, bgcolor.b, 255)

    return self

导致:

First attempt

我也尝试过使用各种混合模式,这是我能得到的最好结果:

@to_texture
def draw_circle(self, x, y, r, color, opacity=1.0, fill=True, aa=False, penwidth=1):

        # ... omitted for brevity

        else:
            # If the penwidth is larger than 1, things become a bit more complex.
            # To ensure that the interior of the circle is transparent, we will
            # have to work with multiple textures and blending.
            outer_r = int(r+penwidth*.5)
            inner_r = int(r-penwidth*.5)

            # Calculate the required dimensions of the separate texture we are
            # going to draw the circle on. Add 1 pixel to account for division
            # inaccuracies (i.e. dividing an odd number of pixels)
            (c_width, c_height) = (outer_r*2+1, outer_r*2+1)

            # Create the circle texture, and make sure it can be a rendering
            # target by setting the correct access flag.
            circle_texture = self.environment.texture_factory.create_sprite(
                size=(c_width, c_height),
                access=sdl2.SDL_TEXTUREACCESS_TARGET
            )

            # Set renderer target to the circle texture
            if sdl2.SDL_SetRenderTarget(self.sdl_renderer, circle_texture.texture) != 0:
                raise Exception("Could not set circle texture as rendering"
                    " target: {}".format(sdl2.SDL_GetError()))

            # Draw the annulus:
            # as the reference point is the circle center, outer_r
            # can be used for the circles x and y coordinates.
            sdlgfx.filledCircleRGBA(self.sdl_renderer, outer_r, outer_r,
                outer_r, color.r, color.g, color.b, opacity)

            # Draw the hole
            sdlgfx.filledCircleRGBA(self.sdl_renderer, outer_r, outer_r,
                inner_r, 0, 0, 0, 255)

            # Set renderer target back to the FrameBuffer texture
            if sdl2.SDL_SetRenderTarget(self.sdl_renderer, self.surface.texture) != 0:
                raise Exception("Could not unset circle texture as rendering"
                    " target: {}".format(sdl2.SDL_GetError()))

            # Use additive blending when blitting the circle texture on the main texture
            sdl2.SDL_SetTextureBlendMode(circle_texture.texture, sdl2.SDL_BLENDMODE_ADD)

            # Perform the blitting operation
            self.renderer.copy( circle_texture, dstrect=(x-int(c_width/2),
                y-int(c_height/2), c_width, c_height) )

    return self

导致:

Second attempt with blending

关闭,但没有雪茄,因为线条现在看起来是在前面而不是在它后面,并且就我理解添加剂/屏幕混合而言,这是预期的行为。

我知道还有函数SDL_SetRenderDrawBlendMode,但sdl2gfx绘图函数似乎忽略了您使用此函数设置的任何内容。

有没有比我更有经验的人以前做过这样的事情,谁能指出我正确的方向来应对这一挑战?

2 个答案:

答案 0 :(得分:1)

我认为你必须创建一个SDL_Surface,为它创建一个软件渲染器,在其上绘制,使用SDL_ColorKey()去除内部颜色,然后将其转换回{{1} }。也许它不是最快的方式,但它的工作原理。您始终可以创建透明的PNG图像并从中加载。

SDL_Texture

enter image description here

答案 1 :(得分:0)

@tntxtnt只是打败了我,但我自己也以类似的方式解决了这个问题。我的解决方案也不能满足仅使用纹理的需求,并且回归到使用表面。我不知道是否可以仅使用纹理来解决这个问题,或者如果使用表面会对性能产生很大影响,但至少可以这样做。

@tntxtnt解决方案非常好,但我也在这里添加我的,因为我有一个稍微不同的方法,依靠pysdl2的实用功能:

<div class="flex-container">
  <div class="flex-child">1</div>
  <div class="flex-child">2</div>
  <div class="flex-child">3</div>
  <div class="flex-child">4</div>
  <div class="flex-child">5</div>
  <div class="flex-child">6</div>
  <div class="flex-child">7</div>
  <div class="flex-child">8</div>
  <div class="flex-child">9</div>
  <div class="flex-child">10</div>
  <div class="flex-child">11</div>
  <div class="flex-child">12</div>
  <div class="flex-child">13</div>
  <div class="flex-child">14</div>
</div>