Pygame click on image (not a rectangle) - python

This is the part of my code, where the problem is:
button = pygame.image.load("button1.png")
screen.blit(button, (100, 100))
This image looks like this:
[
I need to increase a value of a variable, when the user clicks on the image.
I tryed some solutions, but most of them was drawing an "invisible" rectangle over the picture, and the variable's value vas increasing, even if someone clicked on the white space near the triangle.

It's quite easy with the mask module.
From the docs:
Useful for fast pixel perfect collision detection. A mask uses 1 bit per-pixel to store which parts collide.
First, create a Mask from the image
mask = pygame.mask.from_surface(button)
Then, when checking for the mouse click event, check if the point in the mask is set.
Here's a simple example:
import pygame
def main():
pygame.init()
screen = pygame.display.set_mode((480, 320))
button = pygame.image.load('button.png').convert_alpha()
button_pos = (100, 100)
mask = pygame.mask.from_surface(button)
x = 0
while True:
for e in pygame.event.get():
if e.type == pygame.QUIT:
return
if e.type == pygame.MOUSEBUTTONDOWN:
try:
if mask.get_at((e.pos[0]-button_pos[0], e.pos[1]-button_pos[1])):
x += 1
print(x)
except IndexError:
pass
screen.fill((80,80,80))
screen.blit(button, button_pos)
pygame.display.flip()
main()
Example button.png for testing:

There's no easy way to do this in pygame other than manually calculating where the mouse is and figuring out if it's in the triangle or not.
The image you're loading (button1.png) is a square image, and so there's no way for pygame or any other library to know what it's "actual" shape is. You'll either have to do it yourself or be okay with the user being able to click on the white space.

You could use Surface.get_at() to check the color of the pixel where the mouse clicks. If it's the background color (white in your case) you consider it outside, otherwise is inside and you trigger the action.
Here a working example. The insideimage function checks that the click is inside the surface button (the rectangle) and checks the color of the pixel at mouse coordinates. Returns True if the click is inside the surface and the color is not white.
This works if the background color is not used again inside the image.
import sys
import pygame
SCREENWIDTH = 500
SCREENHEIGHT = 500
pygame.init()
screen = pygame.display.set_mode((SCREENWIDTH, SCREENHEIGHT))
button = pygame.image.load("button1.png")
screen.blit(button, (100, 100))
def insideimage(pos, rsurf, refcolor):
"""rsurf: Surface which contains the image
refcolor: background color, if clicked on this color returns False
"""
refrect = rsurf.get_rect().move((100, 100))
pickedcol = screen.get_at(pos)
return refrect.collidepoint(pos) and pickedcol != refcolor
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.MOUSEBUTTONUP:
valid = insideimage(event.pos, button, (255, 255, 255, 255))
#(255, 255, 255, 255) this is color white with alpha channel opaque
print(valid)
pygame.display.update()

Related

Why are there black lines appearing between the red and green squares whenever they "move" [duplicate]

In Pygame, I have wrote a Minesweeper clone. However, when I blit the final image stating YOU LOSE or YOU WIN, I get this result:
I'm sure you notice the thick black line surrounding the text. Here is the function in which the image is blitted onto the window:
def play():
SIZE = (WIDTH, HEIGHT) = (16, 16)
MINES = 40
PIXELS_PER_CELL = 30
pygame.init()
screen = pygame.display.set_mode((WIDTH * PIXELS_PER_CELL,
HEIGHT * PIXELS_PER_CELL))
pygame.display.set_caption("PyMines")
board = create_board(SIZE, MINES)
board.draw(screen)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif (event.type == pygame.MOUSEBUTTONDOWN and board.is_playing and
not board.is_solved):
board.mouse_handler(event, screen)
message = None
if not board.is_playing:
board.show_mines(screen)
message = pygame.image.load("images/lose.png").convert_alpha()
elif board.is_solved:
message = pygame.image.load("images/win.png").convert_alpha()
if message:
message = pygame.transform.scale(message, (screen.get_width(),
screen.get_height() //
5))
screen.blit(message, (0, 0))
pygame.display.update()
As I am not sure which part of the code you should be looking at, here is the full code.
Another reason why I think this behaviour is so bizarre, is that when I first created PyMines, the image blitted perfectly like so (as you can see, there is a very slight shadow to the text):
This however, is not a optimized version, as after each cycle, the whole board is redrawn (so it takes a very long time on a 16x16 board as shown in the first image, so I used a 9x9 - but the results are the same). Here is the play() function of the original version:
def play():
SIZE = (WIDTH, HEIGHT) = (9, 9)
MINES = 10
PIXELS_PER_CELL = 30
pygame.init()
screen = pygame.display.set_mode((WIDTH * PIXELS_PER_CELL,
HEIGHT * PIXELS_PER_CELL))
pygame.display.set_caption("PyMines")
board = create_board(SIZE, MINES)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif (event.type == pygame.MOUSEBUTTONDOWN and board.is_playing and
not board.is_solved):
board.mouse_handler(event, screen)
message = None
if not board.is_playing:
board.show_mines()
message = pygame.image.load("lose.png").convert_alpha()
elif board.is_solved:
message = pygame.image.load("win.png").convert_alpha()
board.draw(screen)
if message:
message = pygame.transform.scale(message, (screen.get_width(),
screen.get_height() //
5))
screen.blit(message, (0, 0))
pygame.display.update()
I would attach a link to the full code, but pastebin is down, so here is the full code for the original game without the strange black line.
EDIT: I have already tried dropping the convert_alpha() and adding convert() or even nothing at all.
.convert():
NOTHING:
Why are all these black lines there, how do I get rid of them and which version (convert/convert_alpha/NOTHING) should I use (and how to decide which one to use).
The text has a black shadow with an alpha channel. In your original version, you render the board, then render the text, and the shadow gets blended with the board.
In the revised version, you render the board, then repeatedly render the text over it. On the first pass, it renders correctly, with the shadow blending with the board. On the second pass, the shadow blends with the shadow you've already rendered, making a slightly darker shadow. On the next pass, the shadow gets slightly darker, and so on.
You can't use alpha blending without keeping tight control over what you're blending over. Each time you render the text, you'll need to render at least the section of the board behind the text, if not the full board.

Is there any way to convert the screen of a pygame gui into an image somehow?

So I'm working on an interactive version of the MNIST handwritten digit image classification project with pygame, where the user draws on the gui and based off of that, the model which I have already made will look at the screen and output a prediction as to what number the image contains. My problem is that I'm not sure what approach to take to make whatever is displayed on the gui into an input of sorts for my model to predict (an image is required as an input)
Here's the code that I made for the gui:
import pygame as pg
#I'm importing a function that I made with my model
#Takes an image input and spits out a prediction as to what the number displayed in the image should be
from MNIST_Classification_GUI import makePrediction
pg.init()
screen = pg.display.set_mode([800, 600])
pg.display.set_caption("Draw a Number")
radius = 10
black = (0, 0, 0)
isGoing = True
screen.fill((255, 255, 255))
last_pos = (0, 0)
def roundline(srf, color, start, end, radius=1):
dx = end[0]-start[0]
dy = end[1]-start[1]
distance = max(abs(dx), abs(dy))
for i in range(distance):
x = int( start[0]+float(i)/distance*dx)
y = int( start[1]+float(i)/distance*dy)
pg.draw.circle(srf, color, (x, y), radius)
#To be used for the popup text containing the prediction
pg.font.init()
myFont = pg.font.SysFont("Sans Serif", 10)
draw_on = False
while isGoing:
for event in pg.event.get():
if event.type == pg.QUIT:
isGoing = False
if event.type == pg.MOUSEBUTTONDOWN:
spot = event.pos
pg.draw.circle(screen, black, spot, radius)
draw_on = True
#This is the part where I want to somehow obtain an image from the gui
#So when the user stops drawing, the popup text appears with the prediction
if event.type == pg.MOUSEBUTTONUP:
draw_on = False
#The makePrediction takes an image input and returns the predicted value
prediction = makePrediction(screen)
textSurface = myFont.render(f"The number should be {prediction}", False, black)
screen.blit(textSurface, (0, 0))
if event.type == pg.MOUSEMOTION:
if draw_on:
pg.draw.circle(screen, black, event.pos, radius)
roundline(screen, black, event.pos, last_pos, radius)
last_pos = event.pos
pg.display.flip()
pg.quit()
The PyGame display (window) is associated to a pygame.Surface object. Use pygame.image.save() to store the content of a Surface to a bitmap. The file type is automatically determined by the file extension:
pygame.image.save(screen, "screenshot.png")

Python3/Pygame: Get the "working resolution" of the monitor (= equally large non-HD screen??) [duplicate]

UPDATED ISSUE
I have discovered the issue appears to be with the fact that I am using the FULLSCREEN display flag to create the window. I added a rectangle to be drawn in the top left of the scree (0, 0), but when I run the program, It is mostly off the screen. Then, when I Alt-Tab away and back, the rectangle is appropriately placed at 0,0 and the turret is off center.
So basically, when the program starts, the game screen is larger than my actual screen, but centered. Then after Alt-Tab, the game screen is lined up with 0,0 but since the game screen is larger than my screen, the turret looks off center, but is actually centered relative to the game.
So the real question is why does using the FULLSCREEN display flag make a screen larger than my computer screen?
ORIGINAL ISSUE
I am building a simple demonstration of a turret in the center of the screen which follows the location of the cursor as if to fire where it is. Everything works perfectly until I Alt-Tab away from the screen, and then Alt-Tab back. At this point to turret is now off center (down and to the right)
import pygame, math
pygame.init()
image_library = {}
screen_dimen = pygame.display.Info()
print("Screen Dimensions ", screen_dimen)
def get_image(name):
if name not in image_library:
image = pygame.image.load(name)
image_library[name] = image
else:
image = image_library[name]
return image
robot_turret_image = get_image('robot_turret.png')
screen = pygame.display.set_mode((0, 0), pygame.`FULLSCREEN`)
done = False
clock = pygame.time.Clock()
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
if event.type == pygame.MOUSEMOTION:
print(event.pos)
if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
done = True
screen.fill((0, 0, 0))
pos = pygame.mouse.get_pos()
angle = 360 - math.atan2(pos[1] - (screen_dimen.current_h / 2),
pos[0] - (screen_dimen.current_w / 2)) * 180 / math.pi
rot_image = pygame.transform.rotate(robot_turret_image, angle)
rect = rot_image.get_rect(center=(screen_dimen.current_w / 2, screen_dimen.current_h / 2))
screen.blit(rot_image, rect)
color = (0, 128, 255)
pygame.draw.rect(screen, color, pygame.Rect(0, 0, 200, 200))
pygame.display.update()
clock.tick(60)
It seems that the center is now off. I have printed out the screen dimensions before and after the Alt-Tab and they are the same, so I can't figure out why the image moves. I believe I am missing something regarding state changes with Pygame, but can't figure out what. If it is relevant, I am on Windows 10.
Alright, I discovered a solution from gamedev.stackexchange
And I will re-hash it here. The issue was that Using the fullscreen tag was making a screen larger than my computer screen. The following code solves this
import ctypes
ctypes.windll.user32.SetProcessDPIAware()
true_res = (ctypes.windll.user32.GetSystemMetrics(0), ctypes.windll.user32.GetSystemMetrics(1))
pygame.display.set_mode(true_res,pygame.FULLSCREEN)
It is important to note that this is potentially just a windows fix, but I do not have another system with which to test it on. But It works on Windows 10 with python 3.5.1 and pygame 1.9.2a0

Pygame FULLSCREEN Display Flag Creates A Game Screen That Is Too Large For The Screen

UPDATED ISSUE
I have discovered the issue appears to be with the fact that I am using the FULLSCREEN display flag to create the window. I added a rectangle to be drawn in the top left of the scree (0, 0), but when I run the program, It is mostly off the screen. Then, when I Alt-Tab away and back, the rectangle is appropriately placed at 0,0 and the turret is off center.
So basically, when the program starts, the game screen is larger than my actual screen, but centered. Then after Alt-Tab, the game screen is lined up with 0,0 but since the game screen is larger than my screen, the turret looks off center, but is actually centered relative to the game.
So the real question is why does using the FULLSCREEN display flag make a screen larger than my computer screen?
ORIGINAL ISSUE
I am building a simple demonstration of a turret in the center of the screen which follows the location of the cursor as if to fire where it is. Everything works perfectly until I Alt-Tab away from the screen, and then Alt-Tab back. At this point to turret is now off center (down and to the right)
import pygame, math
pygame.init()
image_library = {}
screen_dimen = pygame.display.Info()
print("Screen Dimensions ", screen_dimen)
def get_image(name):
if name not in image_library:
image = pygame.image.load(name)
image_library[name] = image
else:
image = image_library[name]
return image
robot_turret_image = get_image('robot_turret.png')
screen = pygame.display.set_mode((0, 0), pygame.`FULLSCREEN`)
done = False
clock = pygame.time.Clock()
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
if event.type == pygame.MOUSEMOTION:
print(event.pos)
if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
done = True
screen.fill((0, 0, 0))
pos = pygame.mouse.get_pos()
angle = 360 - math.atan2(pos[1] - (screen_dimen.current_h / 2),
pos[0] - (screen_dimen.current_w / 2)) * 180 / math.pi
rot_image = pygame.transform.rotate(robot_turret_image, angle)
rect = rot_image.get_rect(center=(screen_dimen.current_w / 2, screen_dimen.current_h / 2))
screen.blit(rot_image, rect)
color = (0, 128, 255)
pygame.draw.rect(screen, color, pygame.Rect(0, 0, 200, 200))
pygame.display.update()
clock.tick(60)
It seems that the center is now off. I have printed out the screen dimensions before and after the Alt-Tab and they are the same, so I can't figure out why the image moves. I believe I am missing something regarding state changes with Pygame, but can't figure out what. If it is relevant, I am on Windows 10.
Alright, I discovered a solution from gamedev.stackexchange
And I will re-hash it here. The issue was that Using the fullscreen tag was making a screen larger than my computer screen. The following code solves this
import ctypes
ctypes.windll.user32.SetProcessDPIAware()
true_res = (ctypes.windll.user32.GetSystemMetrics(0), ctypes.windll.user32.GetSystemMetrics(1))
pygame.display.set_mode(true_res,pygame.FULLSCREEN)
It is important to note that this is potentially just a windows fix, but I do not have another system with which to test it on. But It works on Windows 10 with python 3.5.1 and pygame 1.9.2a0

Is it possible to stretch an image in pygames?

Is it possible to stretch an image in pygame using an event to trigger it?
Like say I have a person and I want his eyes to popout like this
when I press a button and I am using surface.blit(eyes=pygame.image.load('eyes')) for the eyes.
Can i stretch the eye image like the picture in this link?
There is a solution to this problem that allows you to stretch the eyes to the exact width you want, but it may make the eyes very deformed... (nevermind, the original image has pretty deformed eyes anyway.)
From the Pygame doc on pygame.transform.scale:
scale(Surface, (width, height), DestSurface = None) -> Surface
We also use image.get_height() so the user does not have to get the height themselves.
So you would do something like this (wrapped in a function):
def stretchEyes(image, newWidth):
return pygame.transform.scale(image, (image.get_height(), newWidth))
eyes = stretchEyes(eyes, image.get_width()*3) # Stretch to three times width
# Blitting takes in the top left position, so we don't need to do any maths here!
screen.blit(eyes, (x,y))
A better approach would be to have two images one for the actual image and one where the eyes are stretched. Draw the second image whenever you need instead of the first image.
import pygame
from pygame.locals import *
screen = pygame.display.set_mode((540, 480))
runner1 = pygame.image.load('./runner1.jpg').convert()
runner1_rect = runner1.get_rect(center=(270, 240))
runner2 = pygame.image.load('./runner2.jpg').convert()
runner2_rect = runner2.get_rect(center=(270, 240))
screen.fill((0, 0, 0))
change = True
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
if event.type == MOUSEBUTTONDOWN:
screen.blit(runner2, runner2_rect)
pygame.display.update()
if event.type == KEYDOWN:
screen.blit(runner1, runner1_rect)
pygame.display.update()
As shown in the above example, we get images(sprites) of different postures and play them as and when we need to get the motion.
To begin and understand the pygame start from PUMMEL THE CHIMP and there are good API to handle sprites and their behavior through pygame.

Categories