2017-10-10 68 views
2

標題說明了一切。我期望的效果將用於UI,因爲UI泡泡會出現,並且我想爲它們製作拉伸動畫。在Pygame中拉伸圖像,同時保留角落

iOS消息應用程序中的聊天泡泡就是此行爲的一個很好的示例,請參閱 here for example。這裏的重放的主圖像:

enter image description here

通知就過去聊天氣泡靠不住的行爲。這在通訊應用程序中並不常見,正確的伸展是我想用Pygame實現的。

是否有任何簡單的方法來重現Pygame中這種特定類型的拉伸?即使有一些限制,比如說,所有的角落都必須是相同的大小或者其他東西。我只想知道什麼是可能的。

謝謝!

+1

您正在尋找的方式是[9-slicing](https://docs.unity3d.com/Manual/9SliceSprites.html)。我認爲pygame中沒有任何內容可以直接處理9分片圖像。嘗試創建一個擴展pygame的'Sprite'類的類可能會很有趣!你打算通過多個「拆分」圖像,或者你想設置點來處理縮放? – CodeSurgeon

+0

@CodeSurgeon呵呵,是的,我可能會做一個子類。我可能會拿出一個界限的圖像,然後創建9個圖像,然後進行切片。非常感謝這個名字,我無法弄清楚它應該被稱爲什麼。 –

回答

2

根據我在評論中提出的建議,下面是在pygame中創建和渲染9切片精靈的SliceSprite類的實現。我還包括一個樣本來展示如何使用它。 它絕對是粗糙的邊緣(不會檢查無效的輸入,例如當您調整寬度小於定義的左側和右側切片大小的精靈時),但仍應該是一個有用的開始。此代碼已被更新和拋光以處理這些邊界情況,並且不會在@skrx在註釋中建議的每次繪製調用中重新創建九個子表面。

slicesprite.py

import pygame 

class SliceSprite(pygame.sprite.Sprite): 
    """ 
    SliceSprite extends pygame.sprite.Sprite to allow for 9-slicing of its contents. 
    Slicing of its image property is set using a slicing tuple (left, right, top, bottom). 
    Values for (left, right, top, bottom) are distances from the image edges. 
    """ 
    width_error = ValueError("SliceSprite width cannot be less than (left + right) slicing") 
    height_error = ValueError("SliceSprite height cannot be less than (top + bottom) slicing") 

    def __init__(self, image, slicing=(0, 0, 0, 0)): 
     """ 
     Creates a SliceSprite object. 
     _sliced_image is generated in _generate_slices() only when _regenerate_slices is True. 
     This avoids recomputing the sliced image whenever each SliceSprite parameter is changed 
     unless absolutely necessary! Additionally, _rect does not have direct @property access 
     since updating properties of the rect would not be trigger _regenerate_slices. 

     Args: 
      image (pygame.Surface): the original surface to be sliced 
      slicing (tuple(left, right, top, bottom): the 9-slicing margins relative to image edges 
     """ 
     pygame.sprite.Sprite.__init__(self) 
     self._image = image 
     self._sliced_image = None 
     self._rect = self.image.get_rect() 
     self._slicing = slicing 
     self._regenerate_slices = True 

    @property 
    def image(self): 
     return self._image 

    @image.setter 
    def image(self, new_image): 
     self._image = new_image 
     self._regenerate_slices = True 

    @property 
    def width(self): 
     return self._rect.width 

    @width.setter 
    def width(self, new_width): 
     self._rect.width = new_width 
     self._regenerate_slices = True 

    @property 
    def height(self): 
     return self._rect.height 

    @height.setter 
    def height(self, new_height): 
     self._rect.height = new_height 
     self._regenerate_slices = True 

    @property 
    def x(self): 
     return self._rect.x 

    @x.setter 
    def x(self, new_x): 
     self._rect.x = new_x 
     self._regenerate_slices = True 

    @property 
    def y(self): 
     return self._rect.y 

    @y.setter 
    def y(self, new_y): 
     self._rect.y = new_y 
     self._regenerate_slices = True 

    @property 
    def slicing(self): 
     return self._slicing 

    @slicing.setter 
    def slicing(self, new_slicing=(0, 0, 0, 0)): 
     self._slicing = new_slicing 
     self._regenerate_slices = True 

    def get_rect(self): 
     return self._rect 

    def set_rect(self, new_rect): 
     self._rect = new_rect 
     self._regenerate_slices = True 

    def _generate_slices(self): 
     """ 
     Internal method required to generate _sliced_image property. 
     This first creates nine subsurfaces of the original image (corners, edges, and center). 
     Next, each subsurface is appropriately scaled using pygame.transform.smoothscale. 
     Finally, each subsurface is translated in "relative coordinates." 
     Raises appropriate errors if rect cannot fit the center of the original image. 
     """ 
     num_slices = 9 
     x, y, w, h = self._image.get_rect() 
     l, r, t, b = self._slicing 
     mw = w - l - r 
     mh = h - t - b 
     wr = w - r 
     hb = h - b 

     rect_data = [ 
      (0, 0, l, t), (l, 0, mw, t), (wr, 0, r, t), 
      (0, t, l, mh), (l, t, mw, mh), (wr, t, r, mh), 
      (0, hb, l, b), (l, hb, mw, b), (wr, hb, r, b), 
     ] 

     x, y, w, h = self._rect 
     mw = w - l - r 
     mh = h - t - b 
     if mw < 0: raise SliceSprite.width_error 
     if mh < 0: raise SliceSprite.height_error 

     scales = [ 
      (l, t), (mw, t), (r, t), 
      (l, mh), (mw, mh), (r, mh), 
      (l, b), (mw, b), (r, b), 
     ] 

     translations = [ 
      (0, 0), (l, 0), (l + mw, 0), 
      (0, t), (l, t), (l + mw, t), 
      (0, t + mh), (l, t + mh), (l + mw, t + mh), 
     ] 

     self._sliced_image = pygame.Surface((w, h)) 
     for i in range(num_slices): 
      rect = pygame.rect.Rect(rect_data[i]) 
      surf_slice = self.image.subsurface(rect) 
      stretched_slice = pygame.transform.smoothscale(surf_slice, scales[i]) 
      self._sliced_image.blit(stretched_slice, translations[i]) 

    def draw(self, surface): 
     """ 
     Draws the SliceSprite onto the desired surface. 
     Calls _generate_slices only at draw time only if necessary. 
     Note that the final translation occurs here in "absolute coordinates." 

     Args: 
      surface (pygame.Surface): the parent surface for blitting SliceSprite 
     """ 
     x, y, w, h, = self._rect 
     if self._regenerate_slices: 
      self._generate_slices() 
      self._regenerate_slices = False 
     surface.blit(self._sliced_image, (x, y)) 

實施例使用(main.py):

import pygame 
from slicesprite import SliceSprite 

if __name__ == "__main__": 
    pygame.init() 
    screen = pygame.display.set_mode((800, 600)) 
    clock = pygame.time.Clock() 
    done = False 

    outer_points = [(0, 20), (20, 0), (80, 0), (100, 20), (100, 80), (80, 100), (20, 100), (0, 80)] 
    inner_points = [(10, 25), (25, 10), (75, 10), (90, 25), (90, 75), (75, 90), (25, 90), (10, 75)] 
    image = pygame.Surface((100, 100), pygame.SRCALPHA) 
    pygame.draw.polygon(image, (20, 100, 150), outer_points) 
    pygame.draw.polygon(image, (0, 60, 120), inner_points) 

    button = SliceSprite(image, slicing=(25, 25, 25, 25)) 
    button.set_rect((50, 100, 500, 200)) 
    #Alternate version if you hate using rects for some reason 
    #button.x = 50 
    #button.y = 100 
    #button.width = 500 
    #button.height = 200 

    while not done: 
     for event in pygame.event.get(): 
      if event.type == pygame.QUIT: 
       done = True 
     screen.fill((0, 0, 0)) 
     button.draw(screen) 
     pygame.display.flip() 
     clock.tick() 
+1

嘿,你似乎在正確的軌道上,但代碼有問題。我剛剛使用[這個生成的圖像](https://pastebin.com/78KMm7S0)進行了測試,並且在某些地方出現了一些不受歡迎的拉伸。 ---我還建議只創建一次圖像(例如在'__init__'方法中),這樣你就可以在'draw'方法中合併圖像。 – skrx

+0

@skrx我嘗試過使用該形狀,如果我在構造函數中使用切片值「slicing =(25,25,25,25)」創建了我的'SliceSprite'(爲了解釋外部和內部「曲率「)。我想過在'__init__'方法中放置切片圖像,但是如果用戶改變'width','height'或'padding',切片將不會被重新計算。我可以爲每個屬性使用'getter和setter decorators',然後調用'_generate_slices()'這樣的內部函數。這會更好嗎?還是你有其他想法? – CodeSurgeon

+0

啊,是的,它可以正確使用'slicing =(25,25,25,25)'。我不確定使用情況。是的,使用屬性('@ property')或只是getter和setter方法可能是一個很好的解決方案,如果用戶想要縮放圖像。順便說一下,因爲這個類是一個pygame精靈,所以如果你把它放入一個精靈組來繪製它,你也可以省略繪製方法。 – skrx

1

下面是我在其中通過拆分它創建表面的放大版本中的溶液分成三部分,重複地在中間線上傳播。垂直放大可以類似地工作。

import pygame as pg 


def enlarge_horizontal(image, width=None): 
    """A horizontally enlarged version of the image. 

    Blit the middle line repeatedly to enlarge the image. 

    Args: 
     image (pygame.Surface): The original image/surface. 
     width (int): Desired width of the scaled surface. 
    """ 
    w, h = image.get_size() 
    # Just return the original image, if the desired width is too small. 
    if width is None or width < w: 
     return image 
    mid_point = w//2 
    # Split the image into 3 parts (left, mid, right). 
    # `mid` is just the middle vertical line. 
    left = image.subsurface((0, 0, w//2, h)) 
    mid = image.subsurface((mid_point, 0, 1, h)) 
    right = image.subsurface((mid_point, 0, w//2, h)) 
    surf = pg.Surface((width, h), pg.SRCALPHA) 

    # Join the parts (blit them onto the new surface). 
    surf.blit(left, (0, 0)) 
    for i in range(width-w+1): 
     surf.blit(mid, (mid_point+i, 0)) 
    surf.blit(right, (width-w//2, 0)) 
    return surf 


def main(): 
    screen = pg.display.set_mode((800, 800)) 
    clock = pg.time.Clock() 
    image = pg.Surface((100, 100), pg.SRCALPHA) 
    pg.draw.circle(image, (20, 100, 150), (50, 50), 50) 
    pg.draw.circle(image, (0, 60, 120), (50, 50), 45) 

    surfs = [enlarge_horizontal(image, width=i) for i in range(0, 701, 140)] 

    while True: 
     for event in pg.event.get(): 
      if event.type == pg.QUIT: 
       return 

     screen.fill((30, 30, 40)) 
     for i, surf in enumerate(surfs): 
      screen.blit(surf, (20, i*109 + 5)) 
     pg.display.flip() 
     clock.tick(60) 


if __name__ == '__main__': 
    pg.init() 
    main() 
    pg.quit() 
+0

如果您想**平鋪**而不是**拉伸**表面的中心內容,則此方法應該很有用。這可能很重要,具體取決於表面的中心切片(例如,是否有圖案)。 – CodeSurgeon