Resize images while preserving aspect ratio

Question:

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)

Asked By: BlackFenix06

||

Answers:

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()
Answered By: Rabbid76