How do you scale a design resolution to other resolutions with Pygame?

Spent some time tinkering and put together a demo showcasing how you could go about tackling this problem. Find yourself an image for testing and put the string path to that image in the script for the value of imagePath.

The functionality of this script is simple. As you hit either left or right on the arrow keys, the screen resolution cycles through a tuple of acceptable resolutions and the screen resizes accordingly while scaling your test image to the new resolution.

import pygame,sys
from pygame import *
from pygame.locals import *


displayIndex = 0
pygame.init()

##standard 16:9 display ratios
DISPLAYS = [(1024,576),(1152,648),(1280,720),(1600,900),(1920,1080),(2560,1440)] 
screen = pygame.display.set_mode(DISPLAYS[displayIndex])
screen.fill((0,0,0))
### change image path to a string that names an image you'd like to load for testing. I just used a smiley face from google image search.
### Put it in the same place as this file or set up your paths appropriately
imagePath = "Smiley-icon.png"


class Icon(pygame.sprite.Sprite):
    def __init__(self,x,y):
        pygame.sprite.Sprite.__init__(self)
        self.smileyImage = pygame.image.load(imagePath)
        self.image = self.smileyImage.convert_alpha()
        ### need to assume a default scale, DISPLAYS[0] will be default for us
        self.rect = self.image.get_rect()
        self.posX = x
        self.posY = y
        self.rect.x = x
        self.rect.y = y
        self.defaultx = (float(self.rect[2])/DISPLAYS[0][0])*100
        self.defaulty = (float(self.rect[3])/DISPLAYS[0][1])*100
        ## this is the percent of the screen that the image should take up in the x and y planes



    def updateSize(self,):
        self.image = ImageRescaler(self.smileyImage,(self.defaultx,self.defaulty))
        self.rect = self.image.get_rect()
        self.rect.x = self.posX
        self.rect.y = self.posY


def ImageRescaler(image,originalScaleTuple): #be sure to restrict to only proper ratios
    newImage = pygame.transform.scale(image,(int(DISPLAYS[displayIndex][0]*(originalScaleTuple[0]/100)),
                                         int(DISPLAYS[displayIndex][1]*(originalScaleTuple[1]/100))))
    return newImage


def resizeDisplay():
    screen = pygame.display.set_mode(DISPLAYS[displayIndex])
    ## this is where you'd have'd probably want your sprite groups set to resize themselves
    ## Just gonna call it on icon here
    icon.updateSize()


icon = Icon(100,100)
while True:
    screen.fill((0,0,0))
    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            sys.exit()

        if event.type == KEYDOWN:
            if event.key == K_LEFT:
                displayIndex -=1
                if displayIndex < 0:
                    displayIndex = 5
                resizeDisplay()
            elif event.key == K_RIGHT:
                displayIndex+=1
                if displayIndex > 5:
                    displayIndex = 0
                resizeDisplay()




    screen.blit(icon.image,(icon.rect.x,icon.rect.y))  
    pygame.display.update()

The best way to go about this is by downscaling images, in order to preserve image quality. Here are two options:

Option 1

This method is probably the fastest.

  1. Create images to be compatible with the largest resolution you are intending to support.
  2. Create a screen with the user's desired size.

    screen = pygame.display.set_mode((user_x, user_y))
    
  3. Downscale images at load time

    image = pygame.image.load("a.png").convert_alpha()
    pygame.transform.scale(image, (screen.width() / your_max_width, screen.height() / your_max_height), DestSurface=image)
    
  4. Now, just blit them normally. It should run at normal speeds.

Option 2

  1. Create a screen with the user's desired size.

    screen = pygame.display.set_mode((user_x, user_y))
    
  2. Next, create a pygame.Surface with the highest resolution you are intending to support.

    surface = pygame.Surface((1920, 1080))
    
  3. Then, blit everything to that surface.

    surface.blit(image, rect)
    # Etc...
    
  4. After all blits are completed, downscale the surface (as necessary) to whatever resolution the user desires. Preferably, you would only allow resolutions with the same aspect ratio.

    pygame.transform.scale(surface, ((screen.width() / your_max_width, screen.height() / your_max_height), DestSurface=surface)
    
  5. Finally, blit the surface to the screen, and display it.

    screen.blit(surface, (0, 0))
    pygame.display.update()
    

This process (downscaling) allows you to preserve image quality while still allowing the user to choose their screen resolution. It will be slower because you are constantly manipulating images.