How to rotate an image around its center while its scale is getting larger(in Pygame)

后端 未结 2 747
灰色年华
灰色年华 2021-01-22 10:33

I loaded an image and I want it to rotate around its center, while its scale is getting larger. I know how to rotate an image around its center originally, but it\'s hard for me

相关标签:
2条回答
  • 2021-01-22 10:48

    Short answer:

    Store the center of the source image rectangle and update the center of the rotated and zoomed image rectangle after the rotation and zoom operation, by the stored center position. Rotate and zoom the image by pygame.transform.rotozoom():

    def blitRotateCenter(surf, image, topleft, angle):
        
        center = image.get_rect(topleft = topleft).center
        rotated_image = pygame.transform.rotozoom(image, angle, scale)
        new_rect = rotated_image.get_rect(center = center)
    
        surf.blit(rotated_image, new_rect.topleft)
    

    Long Answer:

    For the following examples and explanation I'll use a simple image generated by a rendered text:

    font = pygame.font.SysFont('Times New Roman', 50)
    text = font.render('image', False, (255, 255, 0))
    image = pygame.Surface((text.get_width()+1, text.get_height()+1))
    pygame.draw.rect(image, (0, 0, 255), (1, 1, *text.get_size()))
    image.blit(text, (1, 1))
    

    An image (pygame.Surface) can be rotated by pygame.transform.rotate.

    If that is done progressively in a loop, then the image gets distorted and rapidly increases:

    while not done:
    
        # [...]
    
        image = pygame.transform.rotate(image, 1)
        screen.blit(image, pos)
        pygame.display.flip()
    

    This is cause, because the bounding rectangle of a rotated image is always greater than the bounding rectangle of the original image (except some rotations by multiples of 90 degrees).
    The image gets distort because of the multiply copies. Each rotation generates a small error (inaccuracy). The sum of the errors is growing and the images decays.

    That can be fixed by keeping the original image and "blit" an image which was generated by a single rotation operation form the original image.

    angle = 0
    while not done:
    
        # [...]
    
        rotated_image = pygame.transform.rotate(image, angle)
        angle += 1
    
        screen.blit(rotated_image, pos)
        pygame.display.flip()
    

    Now the image seems to arbitrary change its position, because the size of the image changes by the rotation and origin is always the top left of the bounding rectangle of the image.

    This can be compensated by comparing the axis aligned bounding box of the image before the rotation and after the rotation.
    For the following math pygame.math.Vector2 is used. Note in screen coordinates the y points down the screen, but the mathematical y axis points form the bottom to the top. This causes that the y axis has to be "flipped" during calculations

    Set up a list with the 4 corner points of the bounding box:

    w, h = image.get_size()
    box = [pygame.math.Vector2(p) for p in [(0, 0), (w, 0), (w, -h), (0, -h)]]
    

    Rotate the vectors to the corner points by pygame.math.Vector2.rotate:

    box_rotate = [p.rotate(angle) for p in box]
    

    Get the minimum and the maximum of the rotated points:

    min_box = (min(box_rotate, key=lambda p: p[0])[0], min(box_rotate, key=lambda p: p[1])[1])
    max_box = (max(box_rotate, key=lambda p: p[0])[0], max(box_rotate, key=lambda p: p[1])[1])
    

    Calculate the "compensated" origin of the upper left point of the image by adding the minimum of the rotated box to the position. For the y coordinate max_box[1] is the minimum, because of the "flipping" along the y axis:

    origin = (pos[0] + min_box[0], pos[1] - max_box[1])
    
    rotated_image = pygame.transform.rotate(image, angle)
    screen.blit(rotated_image, origin)
    

    To define a pivot on the original image, the "translation" of the pivot in relation to the upper left of the image has to be calculated and the "blit" position of the image has to be displaced by the translation.

    Define a pivot e.g. in the center of the image:

    pivot = pygame.math.Vector2(w/2, -h/2)
    

    Calculate the translation of the rotated pivot:

    pivot_rotate = pivot.rotate(angle)
    pivot_move   = pivot_rotate - pivot
    

    Finally calculate the origin of the rotated image:

    origin = (pos[0] + min_box[0] - pivot_move[0], pos[1] - max_box[1] + pivot_move[1])
    
    rotated_image = pygame.transform.rotate(image, angle)
    screen.blit(rotated_image, origin)
    

    If the image has to be additionally zoomed, then the zoom has to be taken into account when the origin of the image is calculated:

    move   = (-pivot[0] + min_box[0] - pivot_move[0], pivot[1] - max_box[1] + pivot_move[1])
    origin = (pos[0] + zoom * move[0], pos[1] + zoom * move[1])
    
    rotozoom_image = pygame.transform.rotozoom(image, angle, zoom)
    screen.blit(rotozoom_image, origin)
    

    In the following example program, the function blitRotate does all the above steps and "blit" a rotated image to a surface. pos is the position of the image. originPos is the point on the image which is placed on pos and the pivot.


    Minimal example: repl.it/@Rabbid76/PyGame-RotateAroundPivotAndZoom

    import pygame
    
    pygame.init()
    screen = pygame.display.set_mode((300, 300))
    clock = pygame.time.Clock()
    
    def blitRotate(surf, image, pos, originPos, angle, zoom):
    
        # calcaulate the axis aligned bounding box of the rotated image
        w, h       = image.get_size()
        box        = [pygame.math.Vector2(p) for p in [(0, 0), (w, 0), (w, -h), (0, -h)]]
        box_rotate = [p.rotate(angle) for p in box]
        min_box    = (min(box_rotate, key=lambda p: p[0])[0], min(box_rotate, key=lambda p: p[1])[1])
        max_box    = (max(box_rotate, key=lambda p: p[0])[0], max(box_rotate, key=lambda p: p[1])[1])
    
        # calculate the translation of the pivot 
        pivot        = pygame.math.Vector2(originPos[0], -originPos[1])
        pivot_rotate = pivot.rotate(angle)
        pivot_move   = pivot_rotate - pivot
    
        # calculate the upper left origin of the rotated image
        move   = (-originPos[0] + min_box[0] - pivot_move[0], -originPos[1] - max_box[1] + pivot_move[1])
        origin = (pos[0] + zoom * move[0], pos[1] + zoom * move[1])
    
        # get a rotated image
        rotozoom_image = pygame.transform.rotozoom(image, angle, zoom)
      
        # rotate and blit the image
        surf.blit(rotozoom_image, origin)
    
        # draw rectangle around the image
        pygame.draw.rect (surf, (255, 0, 0), (*origin, *rotozoom_image.get_size()),2)
    
    try:
        image = pygame.image.load('AirPlaneFront1-128.png')
    except:
        text = pygame.font.SysFont('Times New Roman', 50).render('image', False, (255, 255, 0))
        image = pygame.Surface((text.get_width()+1, text.get_height()+1))
        pygame.draw.rect(image, (0, 0, 255), (1, 1, *text.get_size()))
        image.blit(text, (1, 1))
    w, h = image.get_size()
    
    start = False
    angle, zoom = 0, 1
    done = False
    while not done:
        clock.tick(60)
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                done = True
            elif event.type == pygame.KEYDOWN or event.type == pygame.MOUSEBUTTONDOWN:
                start = True
    
        pos = (screen.get_width()/2, screen.get_height()/2)
        
        screen.fill(0)
        blitRotate(screen, image, pos, (w/2, h/2), angle, zoom)
        if start:
            angle += 1
            zoom += 0.01
            if zoom > 5:
                zoom = 1
    
        pygame.draw.line(screen, (0, 255, 0), (pos[0]-20, pos[1]), (pos[0]+20, pos[1]), 3)
        pygame.draw.line(screen, (0, 255, 0), (pos[0], pos[1]-20), (pos[0], pos[1]+20), 3)
        pygame.draw.circle(screen, (0, 255, 0), pos, 7, 0)
    
        pygame.display.flip()
        
    pygame.quit()
    exit()
    
    0 讨论(0)
  • 2021-01-22 11:02

    You need to re-set the image's centre after it is scaled (& rotated) as both of these can change the image size.

    # rotate and zoom the sprite
    self.image = pygame.transform.rotozoom(self.original_image, self.angle, self.scale)
    # reset it back to original centre
    self.rect = self.image.get_rect(center=self.rect.center)
    

    Another thing to consider is to ensure your image-content is centred within itself. (Imagine a rectangle with only content drawn to one-side) the rotation will be centred geometrically, but still look weird visually as it rotates.

    0 讨论(0)
提交回复
热议问题