繁体   English   中英

在保留角落的同时在Pygame中拉伸图像

[英]Stretch an Image in Pygame while preserving the corners

标题说明了一切。 我想要的效果将用于UI,因为会出现UI气泡,并且我希望为其进行动画处理。

iOS消息传递应用程序中的聊天气泡是这种行为的一个很好的例子,例如,请参见此处 这是复制的主要图像:

在此处输入图片说明

注意最后一个聊天气泡的怪异行​​为。 这在消息传递应用程序中是不正常的,并且适当的扩展是我想要使用Pygame实现的。

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

谢谢!

根据我在注释中建议的内容,这是SliceSprite类的实现,该类在pygame中创建并渲染9切片的sprite。 我还提供了一个示例来展示如何使用它。 边缘周围肯定很粗糙(不要检查无效输入,例如当您调整Sprite的宽度小于定义的左右切片大小时),但仍然应该是一个有用的开始。 这段代码已经过更新和完善,可以处理这些极端情况,并且不会按@skrx在注释中的建议在每次绘制调用时都重新创建9个次表面。

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

这是一个解决方案,其中我通过将曲面分为三个部分并反复打折中间线来创建曲面的放大版本。 垂直扩大将类似地工作。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM