2017-03-07 203 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