Search code examples
pythonpython-3.xpygamezoomingpygame-surface

How to zoom in and out of an image pygame and using the mousposition as the center of the zoom


I am having an issue with PyGame i can't resolve. So: my idea is that I have a map I can zoom in/out on. zooming in works fine. But zooming out shows that the rest of the picture got deleted and only the part of the image that was previously visible on the window exists now. This is my code:

import pygame
from pygame.locals import *
import os

class App:
    def __init__(self):
        self.running = True
        self.size = (800,600)

         #create window
        self.window = pygame.display.set_mode(self.size, pygame.DOUBLEBUF | pygame.HWSURFACE | pygame.RESIZABLE)

        #create map
        currentdir = os.path.dirname(os.path.realpath(__file__))
        imagedir = currentdir+'/images/europe.png'
        self.map =  pygame.image.load(imagedir)
        self.maprect = self.map.get_rect()
        self.mapsurface = pygame.Surface(self.size)
        self.mapsurface.blit(pygame.transform.scale(self.map,(self.size)),(0,0))
        self.window.blit(self.mapsurface,(0,0))
        self.scale = 1

        #create window
        pygame.display.flip()

    def on_init(self):
        self.country = Country()

    def on_cleanup(self):
        pygame.quit()
        
    def check_event(self,event):
        if event.type == pygame.QUIT:
            self.running = False
        elif event.type == pygame.VIDEORESIZE:
            self.window = pygame.display.set_mode(event.dict['size'], pygame.DOUBLEBUF | pygame.HWSURFACE | pygame.RESIZABLE)
            self.window.blit(pygame.transform.scale(self.map,(event.dict['size'])),(0,0))
        elif event.type == pygame.MOUSEBUTTONDOWN:
            if event.button == 4:
                zoom = 2
                wnd_w,wnd_h = self.window.get_size()
                zoom_size = (round(wnd_w/zoom), round(wnd_h/zoom))
                zoom_area = pygame.Rect(0,0, *zoom_size)
                pos_x,pos_y = pygame.mouse.get_pos()
                zoom_area.center = (pos_x, pos_y)
                zoom_surf = pygame.Surface(zoom_area.size)
                zoom_surf.blit(self.window, (0, 0), zoom_area)
                zoom_surf = pygame.transform.smoothscale(zoom_surf, (wnd_w, wnd_h))
                self.window.blit(zoom_surf, (0, 0))

            elif event.button == 5:
                zoom = 0.5
                wnd_w,wnd_h = self.window.get_size()
                zoom_size = (round(wnd_w/zoom), round(wnd_h/zoom))
                zoom_area = pygame.Rect(0,0,*zoom_size)
                pos_x,pos_y = pygame.mouse.get_pos()
                zoom_area.center = (pos_x, pos_y)
                zoom_surf = pygame.Surface(zoom_area.size)
                zoom_surf.blit(self.window, (0, 0), zoom_area)
                zoom_surf = pygame.transform.smoothscale(zoom_surf, (wnd_w, wnd_h))
                self.window.blit(zoom_surf, (0, 0))
            pygame.display.flip()

        pygame.display.update()
    def on_execute(self):
        while self.running == True:
            for event in pygame.event.get():
                self.check_event(event)
        self.on_cleanup()

class Country(App):
    def __init__(self):
        super().__init__()
    

start = App()
start.on_init()
start.on_execute()

Here are the screenshots of my problem:

so far so good:

enter image description here

zooming in works fine:

enter image description here

zooming out causes this:

enter image description here


Solution

  • You'll need to scale and blit the original image when you zoom. Use the attribute maprect to define the scaled size and relative location of the map. Add a method blit map that can scale and blit the map. Use the method in the constructor of the class App:

    class App:
        def __init__(self):
            # [...]
    
            self.map =  pygame.image.load(imagedir)
            self.maprect = self.map.get_rect(center = self.window.get_rect().center)
            self.blitmap()
    
            #create window
            pygame.display.flip()
    
        def blitmap(self):
            self.mapsurface = pygame.transform.smoothscale(self.map, self.maprect.size)
            self.window.fill(0)
            self.window.blit(self.mapsurface, self.maprect)
    
        # [...]
    

    When the image is zoomed, calculate the new mapping rectangle (self.maprect) and call the method again:

    class App:
        # [...]
    
        def check_event(self,event):
            if event.type == pygame.QUIT:
                self.running = False
    
            elif event.type == pygame.VIDEORESIZE:
                self.window = pygame.display.set_mode(event.dict['size'], pygame.DOUBLEBUF | pygame.HWSURFACE | pygame.RESIZABLE)
                self.blitmap()
    
            elif event.type == pygame.MOUSEBUTTONDOWN:
                if event.button == 4 or event.button == 5:
                    zoom = 2 if event.button == 4 else 0.5
                    mx, my = event.pos
                    left   = mx + (self.maprect.left - mx) * zoom
                    right  = mx + (self.maprect.right - mx) * zoom
                    top    = my + (self.maprect.top - my) * zoom
                    bottom = my + (self.maprect.bottom - my) * zoom
                    self.maprect = pygame.Rect(left, top, right-left, bottom-top)
                    self.blitmap()
    

    See also Scale and zoom window


    Complete example:

    repl.it/@Rabbid76/PyGame-ZoomInAndOut

    import pygame
    from pygame.locals import *
    import os
    
    class App:
        def __init__(self):
            self.running = True
            self.size = (800,600)
    
             #create window
            self.window = pygame.display.set_mode(self.size, pygame.DOUBLEBUF | pygame.HWSURFACE | pygame.RESIZABLE)
    
            #create map
            currentdir = os.path.dirname(os.path.realpath(__file__))
            imagedir = currentdir+'/images/europe.png'
            self.map =  pygame.image.load(imagedir)
            self.maprect = self.map.get_rect(center = self.window.get_rect().center)
            self.blitmap()
            
            #create window
            pygame.display.flip()
    
        def blitmap(self):
            self.mapsurface = pygame.transform.smoothscale(self.map, self.maprect.size)
            self.window.fill(0)
            self.window.blit(self.mapsurface, self.maprect)
    
        def on_init(self):
            self.country = Country()
    
        def on_cleanup(self):
            pygame.quit()
            
        def check_event(self,event):
            if event.type == pygame.QUIT:
                self.running = False
            
            elif event.type == pygame.VIDEORESIZE:
                self.window = pygame.display.set_mode(event.dict['size'], pygame.DOUBLEBUF | pygame.HWSURFACE | pygame.RESIZABLE)
                self.blitmap()
            
            elif event.type == pygame.MOUSEBUTTONDOWN:
                if event.button == 4 or event.button == 5:
                    zoom = 2 if event.button == 4 else 0.5
                    mx, my = event.pos
                    left   = mx + (self.maprect.left - mx) * zoom
                    right  = mx + (self.maprect.right - mx) * zoom
                    top    = my + (self.maprect.top - my) * zoom
                    bottom = my + (self.maprect.bottom - my) * zoom
                    self.maprect = pygame.Rect(left, top, right-left, bottom-top)
                    self.blitmap()
    
            pygame.display.update()
    
        def on_execute(self):
            while self.running == True:
                for event in pygame.event.get():
                    self.check_event(event)
            self.on_cleanup()
    
    class Country(App):
        def __init__(self):
            super().__init__()
        
    
    start = App()
    start.on_init()
    start.on_execute()