python - 在 Pygame 中拉伸(stretch)图像,同时保留角点

标签 python image python-3.x pygame stretch

标题确实说明了一切。我想要的效果将用于 UI,因为 UI 气泡将会出现,并且我想将它们设置为拉伸(stretch)动画。

iOS 消息应用程序中的聊天气泡就是这种行为的一个很好的例子,请参阅 here for example 。这是复制的主要图像:

enter image description here

注意最后一个聊天气泡的古怪行为。这在消息应用程序中并不常见,而适当的拉伸(stretch)正是我想通过 Pygame 实现的目标。

有没有简单的方法可以在 Pygame 中重现这种特定类型的拉伸(stretch)?即使有一些限制,例如所有角都必须具有相同的大小或其他限制。我只是想知道什么是可能的。

谢谢!

最佳答案

根据我在评论中的建议,这里是 SliceSprite 类的实现,它在 pygame 中创建并渲染 9 切片 Sprite 。我还提供了一个示例来展示如何使用它。 它的边缘绝对是粗糙的(不会检查无效输入,例如当您调整 Sprite 大小时宽度小于定义的左右切片大小),但仍然应该是一个有用的开始。 此代码已经过更新和完善,可以处理这些边缘情况,并且不会按照 @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()

关于python - 在 Pygame 中拉伸(stretch)图像,同时保留角点,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46658554/

相关文章:

image - 删除 Woocommerce 产品图片的 LinkUrl

javascript - 未找到 Webpack 和 React 图像

javascript - 图片未定义onload函数,需要获取高度和宽度

python - str 到 python 3 中的时间对象

python - Python 中如何为 `list` 分配内存?为什么列表的大小与其对象的总和不同?

python - 调整图像大小时使用 OpenCV 进行的面部识别不准确

python - 请求包的 urllib3 部分?

python - Django 服务器错误

python - (Python 3.5.2) 如何找到我的输入是列表中的哪一项?

python - 在pycharm中使用anaconda(导入库报错,更新anaconda和虚拟环境)