2017-10-10 85 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