Search code examples
pythonpygameblitpygame-surface

Pygame: How do I blit and rotate an image to connect two points on the screen?


Here is a test program. I started with two random dots and the line connecting them. Now, I want to take a given image (with x,y dimensions of 79 x 1080) and blit it on top of the guide line. I understand that arctan will give me the angle between the points on a cartesian grid, but because y is backwards the screen (x,y), I have to invert some values. I'm confused about the negating step.

If you run this repeatedly, you'll see the image is always parallel to the line, and sometimes on top, but not consistently.

import math
import pygame
import random
pygame.init()
screen = pygame.display.set_mode((600,600))

#target = (126, 270)
#start = (234, 54)
target = (random.randrange(600), random.randrange(600))
start = (random.randrange(600), random.randrange(600))
BLACK = (0,0,0)
BLUE = (0,0,128)
GREEN = (0,128,0)

pygame.draw.circle(screen, GREEN, start, 15)
pygame.draw.circle(screen, BLUE, target, 15)
pygame.draw.line(screen, BLUE, start, target, 5)                        
route = pygame.Surface((79,1080))
route.set_colorkey(BLACK)
BMP = pygame.image.load('art/trade_route00.png').convert()
(bx, by, bwidth, bheight) = route.get_rect()
route.blit(BMP, (0,0), area=route.get_rect())
# get distance within screen in pixels
dist = math.sqrt((start[0] - target[0])**2 + (start[1] - target[1])**2)
# scale to fit: use distance between points, and make width extra skinny.
route = pygame.transform.scale(route, (int(bwidth * dist/bwidth * 0.05), int( bheight * dist/bheight)))
# and rotate... (invert, as negative is for clockwise)
angle = math.degrees(math.atan2(-1*(target[1]-start[1]), target[0]-start[0]))
route = pygame.transform.rotate(route, angle + 90 )
position = route.get_rect()
HERE = (abs(target[0] - position[2]), target[1]) # - position[3]/2)
print(HERE)
screen.blit(route, HERE)
pygame.display.update()
print(start, target, dist, angle, position)

enter image description here


Solution

  • The main problem

    The error is not due to the inverse y coordinates (0 at top, max at bottom) while rotating as you seems to think. That part is correct. The error is here:

    HERE = (abs(target[0] - position[2]), target[1]) # - position[3]/2)
    

    HERE must be the coordinates of the top-left corner of the rectangle inscribing your green and blue dots connected by the blue line. At those coordinates, you need to place the Surface route after rescaling.

    You can get this vertex by doing:

    HERE = (min(start[0], target[0]), min(start[1], target[1])) 
    

    This should solve the problem, and your colored dots should lay on the blue line.

    A side note

    Another thing you might wish to fix is the scaling parameter of route:

    route = pygame.transform.scale(route, (int(bwidth * dist/bwidth * 0.05), int( bheight * dist/bheight)))
    

    If my guess is correct and you want to preserve the original widht/height ratio in the rescaled route (since your original image is not a square) this should be:

    route = pygame.transform.scale(route, (int(dist* bwidth/bheight), int(dist)))
    

    assuming that you want height (the greater size in the original) be scaled to dist. So you may not need the 0.05, or maybe you can use a different shrinking parameter (probably 0.05 will shrink it too much).