2017-03-07 335 views
0

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

我想实现的东西看起来在概念上很简单,但我不能让它发生。

我想用python中的sdl2绘制圆形轮廓。当然,使用sdl2gfx函数circleRGBA()可以非常简单地实现,但它只允许您绘制线宽为1px的轮廓线。画轮廓更粗的圆看起来不太可能。

我一直在使用透明色键(inspired by this method)做这样的事情之前pygame的面,我知道表面也可在SDL2,但告诫是,我想坚持到更快的纹理,它们不提供颜色键机制。

要更好一点视觉上解释的事情:我想要实现的是:

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绘图函数似乎忽略了用这个函数设置的任何东西。

有没有人比我有更多的经验,他们之前做过这样的事情,谁能指导我如何应对这一挑战的正确方向?

回答

1

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

import sys 
import ctypes 
from sdl2 import * 
import sdl2.sdlgfx as sdlgfx 

if __name__ == "__main__": 
    SDL_Init(SDL_INIT_VIDEO) 

    window = SDL_CreateWindow("Test", 
           SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 
           200, 200, SDL_WINDOW_SHOWN) 
    renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED) 


    ############################################################ 
    surface = SDL_CreateRGBSurface(0, 200, 200, 32, 
            0xff000000, # r mask 
            0x00ff0000, # g mask 
            0x0000ff00, # b mask 
            0x000000ff) # a mask 
    soft_renderer = SDL_CreateSoftwareRenderer(surface) 

    SDL_SetRenderDrawColor(soft_renderer, 255, 255, 0, 255) #yellow background 
    SDL_SetRenderDrawBlendMode(soft_renderer, SDL_BLENDMODE_NONE) 
    SDL_RenderClear(soft_renderer) 
    sdlgfx.filledCircleRGBA(soft_renderer, 100, 100, 100, 0, 255, 255, 255) 
    sdlgfx.filledCircleRGBA(soft_renderer, 100, 100, 80, 255, 255, 0, 255) #yellow 
    SDL_SetColorKey(surface, SDL_TRUE, 0xffff00ff) #yellow colorkey 

    circ = SDL_CreateTextureFromSurface(renderer, surface) 

    SDL_DestroyRenderer(soft_renderer) 
    SDL_FreeSurface(surface) 
    ############################################################ 

    running = True 
    event = SDL_Event() 
    while running: 
     # Event loop 
     while SDL_PollEvent(ctypes.byref(event)) != 0: 
      if event.type == SDL_QUIT: 
       running = False 
       break 
     # Rendering 
     SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255) 
     SDL_RenderClear(renderer) 
     sdlgfx.thickLineRGBA(renderer, 200, 0, 0, 200, 10, 255, 255, 255, 255) 
     SDL_RenderCopy(renderer, circ, None, None) 
     SDL_RenderPresent(renderer); 

    SDL_DestroyTexture(circ) 
    SDL_DestroyRenderer(renderer) 
    SDL_DestroyWindow(window) 
    SDL_Quit() 

enter image description here

+0

谢谢!我认为你是对的,因为这是唯一的方法。你只是打败了我:我也以类似的方式解决了这个问题,并且也会在这里发布我的解决方案。尽管如此,你还是有一个更完整和全面的例子,值得被接受的答案。 –

0

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

@tntxtnt解决方案非常好,但我会通过pysdl2的效用函数依赖多一点这里补充也是我的,因为我有一个稍微不同的方法:

class FrameBuffer(object): 

    # ... omitted for brevity 

    @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) 

     # 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 only a filled circle needs to be drawn, 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, simply use sdl2gfx's 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. 
       # To ensure that the interior of the circle is transparent, we will 
       # have to work with multiple textures and blending. 
       outer_r, inner_r = int(r+penwidth*.5), int(r-penwidth*.5) 

       # Calculate the required dimensions of the extra texture we are 
       # going to draw the circle on. Add 1 pixel to account for division 
       # errors (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_sprite = self.environment.texture_factory.create_software_sprite(
        size=(c_width, c_height), 
       ) 
       # Create a renderer to draw to the circle sprite 
       sprite_renderer = sdl2.ext.Renderer(circle_sprite) 

       # Determine the optimal color key to use for this operation 
       colorkey_color = self.determine_optimal_colorkey(color, self.background_color) 

       # Clear the sprite with the colorkey color 
       sprite_renderer.clear(colorkey_color) 

       # Draw the annulus: 
       sdlgfx.filledCircleRGBA(sprite_renderer.sdlrenderer, outer_r, outer_r, 
        outer_r, color.r, color.g, color.b, 255) 
       # Antialias if desired 
       if aa: 
        for i in range(-1,1): 
         for j in range(2): 
          sdlgfx.aacircleRGBA(sprite_renderer.sdlrenderer, outer_r, 
           outer_r, outer_r+i, color.r, color.g, color.b, 255) 

       # Draw the hole 
       sdlgfx.filledCircleRGBA(sprite_renderer.sdlrenderer, outer_r, outer_r, 
        inner_r, colorkey_color.r, colorkey_color.g, colorkey_color.b, 255) 
       # Antialias if desired 
       if aa: 
        for i in range(0,2): 
         for j in range(2): 
          sdlgfx.aacircleRGBA(sprite_renderer.sdlrenderer, outer_r, 
           outer_r, inner_r+i, color.r, color.g, color.b, 255) 

       # Optimize drawing of transparent pixels 
       sdl2.SDL_SetSurfaceRLE(circle_sprite.surface, 1) 

       # Convert the colorkey to a format understandable by the 
       # SDL_SetColorKey function 
       colorkey = sdl2.SDL_MapRGB(circle_sprite.surface.format, 
        colorkey_color.r, colorkey_color.g, colorkey_color.b) 

       # Set transparency color key 
       sdl2.SDL_SetColorKey(circle_sprite.surface, sdl2.SDL_TRUE, colorkey) 

       # Create a texture from the circle sprite 
       circle_texture = self.environment.texture_factory.from_surface(
        circle_sprite.surface 
       ) 

       # Set the desired transparency value to the texture 
       sdl2.SDL_SetTextureAlphaMod(circle_texture.texture, opacity) 

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

       # Cleanup 
       del(circle_sprite) 
       del(sprite_renderer) 

     return self 

    def determine_optimal_colorkey(self, drawing_color, dev_offset=5): 
     """ Determines the optimal color to use for the transparent color key.""" 

     # If foreground and background colors are not identical, simply return 
     # the background color 
     if drawing_color != self.background_color: 
      return self.background_color 

     if not 1 <= dev_offset <= 25: 
      raise ValueError("dev_offset should be a value between 1 and 25") 

     # Create offset_color 
     offset_color = sdl2.ext.Color(dev_offset, dev_offset, dev_offset, 0) 
     # Create white_color 
     white = sdl2.ext.Color(255,255,255,255) 

     color_key = self.background_color + offset_color 
     if color_key != white: 
      return color_key 
     else: 
      return self.background_color - offset_color 

    def opacity(self, value): 
     """ Convert float values to opacity range between 0 and 255. """ 
     if type(value) == float and 0.0 <= value <= 1.0: 
      # This is maybe a bit iffy, because it does not allow opacity values 
      # in the 0 to 255 range between 0 and 1 (it maybe undesiredly converts 
      # it to value*255). 
      # TODO: Think about a solution for this 
      return int(value*255) 
     elif type(value) in [int, float]: 
      if 0 <= value <= 255: 
       return int(value) 
      else: 
       raise ValueError("Invalid opacity value") 
     else: 
      raise TypeError("Incorrect type or value passed for opacity.") 

    # ... ommitted for brevity