Pygame rotozoom rescaling the image out of bounds - python

I want to display an image of a star that grows bigger by 1% every second.
I use the pygame.transform.rotozoom function to do so.
When using rotozoom, the star indeed grows bigger, but its drawing rectangle doesn't grow enough, making the star blitting out of its rectangle bounds.
The star then appears cut.
The original star.png
The result with the program
I tried searching for a solution to expand the rectangle but I did not succeed.
I also tried pygame.transform.scale but it didn't work at all, the star didn't grow or was distorded depending on how much its dimensions were incremented.
Does anyone has any solution for the drawing rectangle or an alternative ?
Here's my program :
import pygame
import time
pygame.init()
star = pygame.image.load('assets/star.png')
bg = pygame.image.load('assets/background.jpg')
_w = pygame.display.set_mode((480,480))
cpt = 0
Done = False
while not Done :
pygame.time.delay(1000)
pygame.display.flip()
_w.blit(bg, (0,0))
_w.blit(star, (0,0))
star = pygame.transform.rotozoom(star, 0, 1.01)
cpt += 1
if cpt == 20 :
Done = True

You need to scale the original image instead of gradually scaling the scaled image:
scale = 1
while not Done :
# [...]
scaled_star = pygame.transform.rotozoom(star, 0, scale)
scale += 0.01
_w.blit(scaled_star, (0, 0))
# [...]

Related

Pygame doesn't draw rectangles on the screen after a specific threshold

I'm trying to make a visualisation for various sorting algorithms and I was playing around with Pygame to try and draw the rectangles that I need.
In the below code, the user is given multiples inputs: the lowest value of the list to be sorted, the highest value, and the number of elements the list is going to have. The elements are going to be randomly generated.
Then I'm getting the user's screen size so that I can have an appropriate window for the visualisation. Based on the visualisation window and the user's input, I'm setting up the width and height of the rectangles, so that each rectangle has the same width and that they are scaled based on the highest value.
Almost everything is nice and fine with this approach, but there's one thing that I can't figure out. It seems that setting the number of elements (n, in the code below) too high, the rectangles are not being drawn.
My asumption is that after a specific threshold, RECT_W, which is the width of the rectangles, becomes to small for Pygame to draw it.
What options do I have to solve it, except of having the number of elements smaller than a specific value?
import random
import pygame
import color_constants as colors
import ctypes
import copy
from pygame.locals import *
# Read data based on user's input
def readData():
listOfNumbers = []
data = dict()
print("Lowest Value: ")
numLow = int(input())
print("Highest Value: ")
numHigh = int(input())
print("Length of list: ")
n = int(input())
for i in range(0, n):
listOfNumbers.append(random.randint(numLow, numHigh))
origLst = copy.copy(listOfNumbers)
data.update({'lst': origLst})
data.update({'numLow': numLow})
data.update({'numHigh': numHigh})
data.update({'n': n})
data.update({'sorted': listOfNumbers})
return data
if __name__ == "__main__":
data = readData()
# Getting the user's screen size
user32 = ctypes.windll.user32
SCREENSIZE = user32.GetSystemMetrics(0)-100, user32.GetSystemMetrics(1)-100
SCREEN_W = SCREENSIZE[0]
SCREEN_H = SCREENSIZE[1]
# Setting and scaling the size of rectangle based on the number of elements (n)
# and the highest number (numHigh)
RECT_W = SCREEN_W // data['n']
RECT_H = SCREEN_H / (data['numHigh'])
# Setting up the color literals
RED = (255, 255, 255)
GRAY = (0, 0, 0)
pygame.init()
screen = pygame.display.set_mode(SCREENSIZE)
running = True
while running:
for event in pygame.event.get():
if event.type == QUIT:
running = False
screen.fill(GRAY)
for i in range(data['n']):
rect = Rect(i*RECT_W, 0, RECT_W, RECT_H * data['lst'][i])
rect.bottom = SCREEN_H
pygame.draw.rect(screen, RED, rect)
pygame.display.flip()
If data['n'] is greater than SCREEN_W, RECT_W is 0. A coordinate is truncated when drawing. You cannot draw a fraction of a pixel. The size of the rectangle can only be integral (0, 1, 2 ...). Hence, you cannot draw a rectangle with a size less than 1.
You can draw the rectangles on a large surface and scale down the surface. However, the rectangles get blurred. So, this is no good option.

Why are pixels fuzzy in Pygame?

I am just getting started with Pygame, and I did a little test of just printing pixels in random spots. However, I noticed that the pixels don't seem to be single pixels at all, but 'fuzzy' blobs of a few pixels, as shown in the image. Here's the code I used to draw the pixels:
Is there any way to just display single pixels?
Edit: Here's the whole code I used:
import pygame.gfxdraw
import pygame
import random
width = 1000
height = 1000
screen = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()
running = True
while running:
x = random.randint(0,1000)
y = random.randint(0,1000)
pygame.gfxdraw.pixel(screen, x, y, (225,225,225))
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
pygame.display.flip()
clock.tick(240)
more fuzzy pixels
pygame.gfxdraw.pixel(surface, x, y, color) will draw a single pixel on the given surface.
Also you will need to add import pygame.gfxdraw.
EDIT: Full code:
import pygame.gfxdraw
import pygame
import random
width = 1680
height = 1050
screen = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()
running = True
x = [i for i in range(width - 10)]
x_full = [i for i in range(width)]
y = 100
y_full = [i for i in range(height // 2)]
while running:
for i in x:
for j in y_full:
pygame.gfxdraw.pixel(screen, i, j, pygame.Color("red"))
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
pygame.display.flip()
clock.tick(1)
Try to test it this way, I set the window size to fit my monitor resolution so it goes fullscreen. x_full and y should give you horizontal line. And if you subtract, for example, 10 you will get slightly shorter line, and vice-versa with y_full and some random x. Also using (width//2, height//2) will cover exactly quarter of the screen. I think it is accurate and that pygame.gfxdraw.pixel(screen, i, j, pygame.Color("red")) displays only single pixel as it should.
In your code you are using random to display pixels and it adds them 240 per second so you are very fast ending up with bunch of pixels at random positions resulting to have pixels close to each other looking as a "bigger one". I think this is what was happening here. Please someone correct me if I am wrong.
Also make small window e.g. (100, 100) and draw one pixel at (50, 50) this way it can be more easily seen. If you are on windows use magnifier to test it.
IMPORTANT:
While testing this with huge number of pixels do it OUTSIDE of the loop because it will consume much processor power to display them.
Hope this answers your question

Make a rectangle bounce depending on the wall it touched in PYGAME

Im trying to make a rectangle bounce, without going off limits.
I want my rectangle to bounce depending on the wall it touched.
In this code im trying to bounce the rectangle in a 90º angle, but it isn't working.
Im using this to calculate each advance:
rect_x += rectxSpeed
rect_y += rectySpeed
When it reachs the limit
if rect_y>450 or rect_y<0:
rectySpeed=5
rect_y=rectySpeed*-(math.pi/2)
if rect_x>650 or rect_x<0:
rectxSpeed=5
rectx_y=rectxSpeed*-(math.pi/2)
Whole code here:
import pygame
import random
import math
# Define some colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
rect_x= 50.0
rect_y = 50.0
rectxSpeed=5
rectySpeed=5
pygame.init()
# Set the width and height of the screen [width, height]
size = (700, 500)
screen = pygame.display.set_mode(size)
pygame.display.set_caption("My Game")
# Loop until the user clicks the close button.
done = False
# Used to manage how fast the screen updates
clock = pygame.time.Clock()
# -------- Main Program Loop -----------
while not done:
# --- Main event loop
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
# --- Game logic should go here
# --- Screen-clearing code goes here
# Here, we clear the screen to white. Don't put other drawing commands
# above this, or they will be erased with this command.
# If you want a background image, replace this clear with blit'ing the
# background image.
screen.fill(BLACK)
string=str(rect_x)
string2=str(rect_y)
string3="["+string+"]"+"["+string2+"]"
font = pygame.font.SysFont('Calibri', 25, True, False)
text = font.render(string3,True,RED)
screen.blit(text, [0, 0])
#Main rectangle
pygame.draw.rect(screen, WHITE, [rect_x, rect_y, 50, 50])
#Second rectangle inside the rectangle 1
pygame.draw.rect(screen, RED, [rect_x+10, rect_y+10, 30, 30])
rect_x += rectxSpeed
rect_y+=rectySpeed
if rect_y>450 or rect_y<0:
rectySpeed=5
rect_y=rectySpeed*-(math.pi/2)
if rect_x>650 or rect_x<0:
rectxSpeed=5
rect_x=rectxSpeed*-(math.pi/2)
# --- Drawing code should go here
# --- Go ahead and update the screen with what we've drawn.
pygame.display.flip()
# --- Limit to 60 frames per second
clock.tick(20)
# Close the window and quit.
pygame.quit()
¿How can i adjust the advance?
This code produce this:
By Changing the reach limit code with:
if rect_y>450 or rect_y<0:
rectySpeed=rectySpeed*-(math.pi/2)
if rect_x>650 or rect_x<0:
rectxSpeed=rectxSpeed*-(math.pi/2)
Produces this:
I think it's important to recognize here that the speed of the rectangle object here is a scalar value, not it's vector counterpart velocity. You are attempting to multiply the rectySpeed (a scalar value) by -(math/pi)/2, which is a value that will be returned in radians as #0x5453 mentioned. As far as the rectangle bouncing differently depending on the wall that it contacted, you have not specified the differing constraints that you intend to impose. I think you may want to consider why you want the rectangle to always bounce at a 90° angle. Note that a rectangle that always bounces at a 90° angle would yield some very odd functionality to the player/user.
The angle that the rectangle is approaching the wall measured from the horizontal will be equal to the angle that the rectangle will rebound off the wall measured from the vertical line of the wall to it's new path (in the case of an x-directional bounce).
In terms of an accurate physics engine, you may want to consider just simplifying your mechanics to the following in the case that the rectangle contacts a wall:
if rect_y>450 or rect_y<0:
rectySpeed=rectySpeed*-1
if rect_x>650 or rect_x<0:
rectxSpeed=rectxSpeed*-1
The above ensures that the rectangle object simply changes direction, therefore the magnitude of the rectangle's speed does not change, however the velocity indeed does since it is a vector quantity.

Efficiently masking a surface in pygame

I need to draw a circle filled with random gray colors and a black outline using pygame. This is what it should look like:
The radius increases by expansion_speed * dt every frame and the surface is updated 60 times per second, so however this is achieved (if even possible) needs to be fast. I tried masking an stored texture but that was too slow. My next idea was to read the pixels from this stored texture and only replace the difference between the last and current surfaces. I tried this too but was unable to translate the idea to code.
So how can this be done?
See my update to your previous related question. It has some info about performance. You could try to enable hardware acceleration in fullscreen mode, but I never personally tried it, so can't give good advice how to do it properly. Just use two differnt colorkeys for extracting circle from noise and putting the whole surface to the display. Note that if your Noise surface has pixels same as colorkey color then they also become transparent.
This example I think is what you are trying to get, move the circle with mouse and hold CTRL key to change radius.
Images:
import os, pygame
pygame.init()
w = 800
h = 600
DISP = pygame.display.set_mode((w, h), 0, 24)
clock = pygame.time.Clock( )
tile1 = pygame.image.load("2xtile1.png").convert()
tile2 = pygame.image.load("2xtile2.png").convert()
tw = tile1.get_width()
th = tile1.get_height()
Noise = pygame.Surface ((w,h))
Background = pygame.Surface ((w,h))
for py in range(0, h/th + 2) :
for px in range(0, w/tw + 2):
Noise.blit(tile1, (px*(tw-1), py*(th-1) ) )
Background.blit(tile2, (px*(tw-1), py*(th-1) ) )
color_key1 = (0, 0, 0)
color_key2 = (1, 1, 1)
Circle = pygame.Surface ((w,h))
Circle.set_colorkey(color_key1)
Mask = pygame.Surface ((w,h))
Mask.fill(color_key1)
Mask.set_colorkey(color_key2)
strokecolor = (10, 10, 10)
DISP.blit(Background,(0,0))
def put_circle(x0, y0, r, stroke):
pygame.draw.circle(Mask, strokecolor, (x0,y0), r, 0)
pygame.draw.circle(Mask, color_key2, (x0,y0), r - stroke, 0)
Circle.blit(Noise,(0,0))
Circle.blit(Mask,(0,0))
dirtyrect = (x0 - r, y0 - r, 2*r, 2*r)
Mask.fill(color_key1, dirtyrect)
DISP.blit(Circle, (0,0))
X = w/2
Y = h/2
R = 100
stroke = 2
FPS = 25
MainLoop = True
pygame.mouse.set_visible(False)
pygame.event.set_grab(True)
while MainLoop :
clock.tick(FPS)
pygame.event.pump()
Keys = pygame.key.get_pressed()
MR = pygame.mouse.get_rel() # get mouse shift
if Keys [pygame.K_ESCAPE] :
MainLoop = False
if Keys [pygame.K_LCTRL] :
R = R + MR[0]
if R <= stroke : R = stroke
else :
X = X + MR[0]
Y = Y + MR[1]
DISP.blit(Background,(0,0))
put_circle(X, Y, R, stroke)
pygame.display.flip( )
pygame.mouse.set_visible(True)
pygame.event.set_grab(False)
pygame.quit( )
Many years ago we had a font rendering challenge with the Pygame project.
Someone created an animated static text for the contest but it was far too slow.
We put our heads together and made a much quicker version. Step one was to create a smallish image with random noise. Something like 64x64. You may need a bigger image if your final image is large enough to notice the tiling.
Every frame you blit the tiled noise using a random offset. Then you take an image with the mask, in your case an inverted circle, and draw that on top. That should give you a final image containing just the unmasked noise.
The results were good. In our case it was not noticeable that the noise was just jittering around. That may be because the text did not have a large unobstrcted area. I'd be concerned your large circle would make the trick appear obvious. i guess if you really had a large enough tiled image it would still work.
The results and final source code are still online at the Pygame website,
http://www.pygame.org/pcr/static_text/index.php

Texturing drawn shapes - python pygame

Im currently using the random import to create five random x, y values and taking those values and drawing a polygon with the pygame.draw.polygon () command. If I had a texture square I wanted to apply over top of that shape instead of having just on rgb value what would be the most efficient way to do that? i want to take the generated polygon below and with out hard coding its shape, taking a general texture square and making all that green that new texture as if that shape was cut out of the texture square.
import pygame,random
from pygame import*
height = 480
width = 640
#colors
red = (255,0,0)
green = (0,255,0)
blue = (0,0,255)
white = (255,255,255)
black = (0,0,0)
pygame.init()
points = [ ]
screen = pygame.display.set_mode((width,height))
pygame.display.set_caption("PlayBox")
r = random
for i in range(0,5):
x = r.randrange(0,640)
y = r.randrange(0,480)
points.append([x,y])
running = True
while running == True:
screen.fill(white)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
break
pygame.draw.polygon(screen,green,points,0)
pygame.display.update()
pygame.display.update()
One option, of course, would be to re-implement the "bucket fill" algorithm yourself,
and copy pixels inside the polygon. That would be a lot of work, and wouldget slow done in pure Python - still, it would launch you into the basic foundations of image manipulation http://en.wikipedia.org/wiki/Flood_fill
Since Pygame already does the heavy lifting, but provides just solid color fills,
the way to go is to use pygame's results as a clipping mask to your texture. Unfortunatelly that is probably more difficult than it should. I hope my sample here
can be useful for others having the same needs.
Pygame gives us some primitives to manipulate the color planes in the surfaces,
but they are definitely low level. Another thing is that these primitives require
numpy to be installed - I am not certain if Window's pyagames installer include it -
otherwise people running your project have to be told to install numpy themselves.
So, teh way to go is:
Load your desired texture in a surface (for less headache, one of the same size
of the final image), to draw the shape you want to be painted with the texture
in a mask surface, with 8bpp (B&W) - which works as a transparency map to the
texture -
them use pygame's surfarray utilities to blit everything together:
# coding: utf-8
import random
import pygame
SIZE = 800,600
def tile_texture(texture, size):
result = pygame.Surface(size, depth=32)
for x in range(0, size[0], texture.get_width()):
for y in range(0, size[1], texture.get_height()):
result.blit(texture,(x,y))
return result
def apply_alpha(texture, mask):
"""
Image should be a 24 or 32bit image,
mask should be an 8 bit image with the alpha
channel to be applied
"""
texture = texture.convert_alpha()
target = pygame.surfarray.pixels_alpha(texture)
target[:] = pygame.surfarray.array2d(mask)
# surfarray objets usually lock the Surface.
# it is a good idea to dispose of them explicitly
# as soon as the work is done.
del target
return texture
def stamp(image, texture, mask):
image.blit(apply_alpha(texture, mask), (0,0))
def main():
screen = pygame.display.set_mode(SIZE)
screen.fill((255,255,255))
texture = tile_texture(pygame.image.load("texture.png"), SIZE)
mask = pygame.Surface(SIZE, depth=8)
# Create sample mask:
pygame.draw.polygon(mask, 255,
[(random.randrange(SIZE[0]), random.randrange(SIZE[1]) )
for _ in range(5)] , 0)
stamp(screen, texture, mask)
pygame.display.flip()
while not any(pygame.key.get_pressed()):
pygame.event.pump()
pygame.time.delay(30)
if __name__ == "__main__":
pygame.init()
try:
main()
finally:
pygame.quit()

Categories

Resources