Search code examples
pythonpython-3.xmathpygamepython-3.9

Resize images while preserving aspect ratio


I have a small problem that could have a simple solution, but unfortunately I'm not very good at math.

I have three images that need to be stacked on top of each other and their heights add up to more than the screen height.

So to fix, I did a simple proportion and changed the height of the three images, like this (it's hypothetical, not the actual code):

new_img1.height = img1.height * screen.height // (img1.height + img2.height + img3.height)

The problem I'm having is doing the same thing, but with the width, considering all three images have the same width.

What I want is that the three images always have the same width as originally, but resized with the new height (so that the three images are proportionally smaller in both dimensions)

I've made several attempts, but my mathematical limits don't help me much XD

How should I fix? Ah, I'm using Python 3.9 with Pygame (although for the latter I don't think it needed to know)


Solution

  • If you want to keep the aspect ratio, you must scale the width and height of the image with the same scaling factor:

    scale_factor = screen.height // (img1.height + img2.height + img3.height) 
    new_img1.width = img1.width * scale_factor
    new_img1.height = img1.height * scale_factor 
    new_img2.width = img2.width * scale_factor
    new_img2.height = img2.height * scale_factor 
    new_img3.width = img3.width * scale_factor
    new_img3.height = img3.height * scale_factor 
    

    If you want all images to have the same width, you must first calculate the scale factor for each image to scale the width of the images. Then you can calculate the scale factor for the height:

    max_width = max(img1.width, img2.width, img3.width)
    scale_factor1 = max_width / img1.width
    scale_factor2 = max_width / img2.width
    scale_factor3 = max_width / img3.width
    height_scale = screen.height / (img1.height * scale_factor1 + img2.height * scale_factor2 + img3.height * scale_factor3)
    scale_factor1 *= height_scale
    scale_factor2 *= height_scale
    scale_factor3 *= height_scale
    

    Minimal example to demonstrate the algorithm:

    import pygame, random
    
    pygame.init()
    window = pygame.display.set_mode((400, 400))
    clock = pygame.time.Clock()
    
    update_rects = True
    run = True
    while run:
        clock.tick(100)
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                run = False 
            if event.type == pygame.KEYDOWN:
                update_rects = True
    
        if update_rects:
            rect1 = pygame.Rect(0, 0, random.randrange(50, 100), random.randrange(50, 100))
            rect2 = pygame.Rect(0, 0, random.randrange(50, 100), random.randrange(50, 100))
            rect3 = pygame.Rect(0, 0, random.randrange(50, 100), random.randrange(50, 100))
            update_rects = False
    
        max_width = max(rect1.width, rect2.width, rect2.width)
        scale_factor1 = max_width / rect1.width
        scale_factor2 = max_width / rect2.width
        scale_factor3 = max_width / rect3.width
        height_scale = window.get_height() / (rect1.height * scale_factor1 + rect2.height * scale_factor2 + rect3.height * scale_factor3)
        scale_factor1 *= height_scale
        scale_factor2 *= height_scale
        scale_factor3 *= height_scale
        
        rect1.width *= scale_factor1
        rect1.height *= scale_factor1
        rect2.width *= scale_factor2
        rect2.height *= scale_factor2
        rect3.width *= scale_factor3
        rect3.height *= scale_factor3
    
        rect2.top = rect1.bottom
        rect3.top = rect2.bottom
    
        window.fill(0)
        pygame.draw.rect(window, "red", rect1)    
        pygame.draw.rect(window, "blue", rect2)
        pygame.draw.rect(window, "yellow", rect3)    
        pygame.display.flip()
    
    pygame.quit()
    exit()