Desperately trying to understand pygame translucency - python

I have been working directionlessly for the past little while trying to figure out how to make a simple function that draws a translucent circle to a given surface in pygame. I did my research, and I found that many people suggested I simply draw the circle to a temporary surface with SRCALPHA enabled, then blit that surface ontop of the real one I'm drawing to. But I thought that's what I implemented below, no?
import pygame
SCREEN_DIMENSIONS = w, h = 800, 600
screen = pygame.display.set_mode(SCREEN_DIMENSIONS)
FPS = 60
clock = pygame.time.Clock()
def draw_alpha_circle(screen, colour, position, radius, thickness=0):
*colour, alpha = colour # Separate the colour from the alpha
# Note: (assuming colour is a 4-tuple (r, g, b, a))
# *colour, alpha = colour
#
# is equivalent to:
# r, g, b, alpha = colour; colour = r, g, b
x, y = position
d = 2*radius
temp_surface = pygame.Surface((d, d))
temp_surface.set_alpha(alpha)
pygame.draw.circle(temp_surface, colour, position, radius, thickness)
screen.blit(temp_surface, (x - radius, y - radius))
running = True
while running:
clock.tick(FPS)
screen.fill((0, 0, 0))
for evt in pygame.event.get():
if evt.type == pygame.QUIT:
running=False
draw_alpha_circle(screen, (255, 0, 0, 128), (w//2, h//2), 20)
pygame.display.update()
pygame.quit()
This actually draws nothing to the screen at all. I'm completely stumped as to what's causing it to draw absolutely nothing. Could someone give me a hand please? If it's any help, I'm running Python 3.2.3.
As a side question; why is pygame translucency such a hassle to understand? Everything else in the engine is fairly straightforwards, which it should be, but this is remarkably under-documented and difficult to use in my opinion.
EDIT: Now I'm confused, because not even the following code works correctly:
def draw_alpha_circle(screen, colour, position, radius, thickness=0):
*colour, alpha = colour # Separate the colour from the alpha
# Note: (assuming colour is a 4-tuple (r, g, b, a))
# *colour, alpha = colour
#
# is equivalent to:
# r, g, b, alpha = colour; colour = r, g, b
x, y = position
d = 2*radius
temp_surface = pygame.Surface((d, d))
# Doesn't even draw real alpha, I just wanted to test out if it draws properly without alpha, which it doesn't.
pygame.draw.circle(temp_surface, colour, position, radius, thickness)
screen.blit(temp_surface, (x - radius, y - radius))
What is going on here? I am utterly perplexed! Am I going insane, and are you guys actually getting circles and I'm just thinking there aren't circles? I swear this pygame translucency is out to get me.

...I'm an idiot.
The fix is to draw the circle on the temp surface NOT AT THE POSITION ON THE NORMAL SURFACE, but at the position (radius, radius). The following function works perfectly.
def draw_alpha_circle(screen, colour, position, radius, thickness=0):
*colour, alpha = colour # Separate the colour from the alpha
# Note: (assuming colour is a 4-tuple (r, g, b, a))
# *colour, alpha = colour
#
# is equivalent to:
# r, g, b, alpha = colour; colour = r, g, b
x, y = position
d = 2*radius
temp_surface = pygame.Surface((d, d))
temp_surface.set_alpha(alpha)
pygame.draw.circle(temp_surface, colour, (radius, radius), radius, thickness)
screen.blit(temp_surface, (x - radius, y - radius))
I'm not going to delete this question however, I'm going to leave it as a monument to my dumbassery.

Related

How Can I Make a Thicker Bezier in Pygame?

I'm building a specialized node editor in Pygame. Each node will be connected with a bezier curve. This curve is built by clicking on a node first. A bezier is drawn between the mouse cursor and the node and once you click on a second node, the bezier line is fixed. My code can already draw the curve and follow the mouse cursor. My problem is that the curve is too thin. Does anyone know of a way to easily specify width in pygame.gfxdraw.bezier? Also, I have no idea what the argument "6" corresponds to; I only know the code won't function without it.
# This draws the bezier curve for the node editor
x, y = pygame.mouse.get_pos()
b_points = [(380,390),(410,385),(425,405), (425, y), (x, y)]
pygame.gfxdraw.bezier(screen, b_points, 6, blue)
Simple answer: You cant't, at least not with pygame.gfxdraw
or pygame.draw. You have to do it yourself.
Compute points along the curve and connect them with pygame.draw.lines.
See Finding a Point on a Bézier Curve: De Casteljau's Algorithm and create a function that draw a bezier curve piont, by point:
import pygame
def ptOnCurve(b, t):
q = b.copy()
for k in range(1, len(b)):
for i in range(len(b) - k):
q[i] = (1-t) * q[i][0] + t * q[i+1][0], (1-t) * q[i][1] + t * q[i+1][1]
return round(q[0][0]), round(q[0][1])
def bezier(surf, b, samples, color, thickness):
pts = [ptOnCurve(b, i/samples) for i in range(samples+1)]
pygame.draw.lines(surf, color, False, pts, thickness)
Minimal example:
import pygame, pygame.gfxdraw
def ptOnCurve(b, t):
q = b.copy()
for k in range(1, len(b)):
for i in range(len(b) - k):
q[i] = (1-t) * q[i][0] + t * q[i+1][0], (1-t) * q[i][1] + t * q[i+1][1]
return round(q[0][0]), round(q[0][1])
def bezier(surf, b, samples, color, thickness):
pts = [ptOnCurve(b, i/samples) for i in range(samples+1)]
pygame.draw.lines(surf, color, False, pts, thickness)
pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
x, y = pygame.mouse.get_pos()
b_points = [(380,390), (410,385), (425,405), (425, y), (x, y)]
screen.fill(0)
bezier(screen, b_points, 20, (255, 255, 0), 6)
pygame.draw.lines(screen, (255, 255, 255), False, b_points, 1)
pygame.gfxdraw.bezier(screen, b_points, 6, (255, 0, 0))
pygame.display.flip()
clock.tick(60)
pygame.quit()

How do I color in specific blocks for a chess board? [duplicate]

This question already has an answer here:
Nested for loop chess board coloring not working Python
(1 answer)
Closed 1 year ago.
I made a chess board, but I need help with coloring the different squares. I only have white squares but I need black squares. Here is my code.
import pygame
def board():
width=480 # measurements for the window
height=480
block_size=59
window = pygame.display.set_mode((width,height))
background_color = (0,0,0) # This is how I make the lines
window.fill(background_color)
for y in range(height):
for x in range(width):
rect = pygame.Rect(x*(block_size+1), y*(block_size+1), block_size, block_size)
pygame.draw.rect(window, (255,255,255), rect) # Leaves space for lines to be visible.
pygame.display.flip()
board()
# It's just the board.
I already know I violated PEP 8.
You can do it like: I changed your coord-manipulation to benefit directly from the given range values not using a mult inside it. Color is flipped on each rect drawn and also on line-change to get alternating row colorings:
import pygame
def board():
def flipColor(color):
white = (240,240,240)
black = (30,30,30)
if not color or color == white:
color = black
else:
color = white
return color
width=480 # measurements for the window
height=480
block_size= 60
window = pygame.display.set_mode((width,height))
background_color = (0,0,0) # This is how I make the lines
window.fill(background_color)
c = None
pygame.draw.rect(window,(255,0,0),pygame.Rect(0,0,width,height)) # red background
for y in range(0,height,block_size):
c = flipColor(c)
for x in range(0,width,block_size):
c = flipColor(c)
rect = pygame.Rect(x , y , x+block_size , y+block_size )
pygame.draw.rect(window, c , rect, 0) # Leaves space for lines to be visible.
for i in range(0,height+1,block_size):
pygame.draw.line(window,(233,33,187),(i,0),(i,width),2)
pygame.draw.line(window,(233,33,187),(0,i),(height,i),2)
pygame.draw.line(window,(233,33,187),(height-2,0),(height-2,width),2) # fix for out of window line
pygame.draw.line(window,(233,33,187),(0,width-2),(height,width-2),2) # fix for out of wondow line
pygame.display.flip()
board()
You can also use itertools.cycle, pass an iterable with the colors and then just call next to cycle through them. I'd create the background surface when the program starts and then just blit it in the while loop. If you need a background with extra lines, just draw them on the same background surface as well or create a copy.
import itertools
import pygame as pg
pg.init()
screen = pg.display.set_mode((480, 480))
clock = pg.time.Clock()
width, height = screen.get_size()
block_size = 60
# Create a surface onto which we'll blit the rectangles.
background = pg.Surface((width, height))
colors = itertools.cycle((pg.Color('white'), pg.Color('black')))
for y in range(0, height, block_size):
for x in range(0, width, block_size):
rect = (x, y, block_size, block_size)
pg.draw.rect(background, next(colors), rect)
next(colors) # Skip the next color.
# Then you can just blit the background in the while loop.
screen.blit(background, (0, 0))
pg.display.flip()
You can also calculate the color.
If the index of the column and row are both equal or not equal the color is white, else black.
For example field A8:
Index of A is 0 --> equal
8 --> equal
==> Field is white
Example field A1:
Index of A is 0 --> equal
1 --> unequal
==> Field is black

Why is pixel manipulation with pygame.surfarray slower than with pygame.image? [duplicate]

I'm looking for method that allow me to draw single pixel on display screen. For example when I click mouse, I want the position of clicked pixel to change color. I know how to read mouse pos, but I could not find simple pixel draw ( there is screen.fill method but it's not working as I want).
You can do this with surface.set_at():
surface.set_at((x, y), color)
You can also use pygame.gfxdraw.pixel():
from pygame import gfxdraw
gfxdraw.pixel(surface, x, y, color)
Do note, however, the warning:
EXPERIMENTAL!: meaning this api may change, or dissapear in later
pygame releases. If you use this, your code will break with the next
pygame release.
You could use surface.fill() to do the job too:
def pixel(surface, color, pos):
surface.fill(color, (pos, (1, 1)))
You can also simply draw a line with the start and end points as the same:
def pixel(surface, color, pos):
pygame.draw.line(surface, color, pos, pos)
The usual method of drawing a point on a Surface or the display is to use [`pygame.Surface.set_at']:
window_surface.set_at((x, y), my_color)
However, this function is very slow and leads to a massive lack of performance if more than 1 point is to be drawn.
Minimal example where each pixel is set separately: repl.it/#Rabbid76/PyGame-DrawPixel-1
import pygame
pygame.init()
window = pygame.display.set_mode((300, 300))
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.fill(0)
rect = pygame.Rect(window.get_rect().center, (0, 0)).inflate(*([min(window.get_size())//2]*2))
for x in range(rect.width):
u = x / (rect.width - 1)
color = (round(u*255), 0, round((1-u)*255))
for y in range(rect.height):
window.set_at((rect.left + x, rect.top + y), color)
pygame.display.flip()
pygame.quit()
exit()
Another option is to use a "pygame.PixelArray" object. This object enables direct pixel access to Surface objects. A PixelArray pixel item can be assigned directly. The pixel can be accessed by subscription. The PixelArray locks the Surface, You have to close() it when you have changed the pixel:
pixel_array = pygame.PixelArray(window_surface)
pixel_array[x, y] = my_color
pixel_array[start_x:end_x, start_y:end_y] = my_color
pixel_array.close()
Minimal example that set one line of pixels at once: repl.it/#Rabbid76/PyGame-DrawPixel-2
import pygame
pygame.init()
window = pygame.display.set_mode((300, 300))
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.fill(0)
rect = pygame.Rect(window.get_rect().center, (0, 0)).inflate(*([min(window.get_size())//2]*2))
pixel_array = pygame.PixelArray(window)
for x in range(rect.width):
u = x / (rect.width - 1)
color = (round(u*255), 0, round((1-u)*255))
pixel_array[rect.left + x, rect.top:rect.bottom] = color
pixel_array.close()
pygame.display.flip()
pygame.quit()
exit()
For those who are interested in a more modern answer to the question you can use pygame.draw.circle() to draw a single pixel at a given position (or center).
pygame.draw.circle(surface, color, center, 0)
The documentation specifically says:
radius (int or float) -- radius of the circle, measured from the center parameter, a radius of 0 will only draw the center pixel
One way of doing that is to draw a line staring and ending at the same point.
pygame.draw.line(surface, (255,255,255), (x,y), (x,y))
draw a single coloured pixel
def drawPoint(x,y,color):
s = pygame.Surface((1,1)) # the object surface 1 x 1 pixel (a point!)
s.fill(color) # color as (r,g,b); e.g. (100,20,30)
# now get an object 'rectangle' from the object surface and place it at position x,y
r,r.x,r.y = s.get_rect(),x,y
screen.blit(s,r) # link the object rectangle to the object surface
of course you have to call: pygame.display.update() once you have
drawn all the points you need, don't call update at every single point.
# with this function, you can draw points and change the yer size
def point(surface, color, x, y, size):
'''the surface need the information of the pygame window'''
for i in range(0, size):
pygame.draw.line(surface, color, (x, y-1), (x, y+2), abs(size))

Does anybody know how to get the X and Y of a drawn circle?

I can't figure out how to get the X and Y coordinates of a drawn circle
(example: pygame.draw.circle(Surface, color, pos(x,y), radius, width=0))
How could I get the X and y and use it in possibly making another circle go towards those codinates? if anybody knows how it would help a lot...
I am a bit baffled tbh.
import pygame
x, y = 100, 100
r = 50
black = (0, 0, 0)
red = (255, 0, 0)
(... screen stuff ...)
pygame.draw.circle(screen, black, (x,y), r)
pygame.draw.circle(screen, red, (x,y), r / 2)
You should already have the coordinates somewhere in your program. If you want to re-use them, you're going to have to store the coordinates into variables, then you can mess around with the coordinates. So just store the coordinates and draw a second one from there. Hope this helps.

Creating a (R,G,B) Sierpinski Triangle in python, using pygame window. Each corner is, respectively Red, Green, and Blue and gradient into each other

So, for my computer science class we are supposed to import the pygame from the website:
http://www.petercollingridge.co.uk/image-processing-pygame
Then we are supposed to create the sierpinski triangle in python in the pygame window using pixels. So each pixel in the window needs to be colored in the shape of the triangle. I can't even get my triangle to show up in just black pixels, and what we're supposed to do is get it to appear with the top corner as red, the bottom left as green, and the bottom right as blue. These colors are supposed to slowly fade into each other (gradient) throughout the triangle. The finished process is supposed to look something like this:
http://eldar.mathstat.uoguelph.ca/dashlock/ftax/Gallery/Sierpenski2D960.gif
First off, everytime I set the window up it says that the midPoint in my main function, where I call the earlier midPoint function is not assigned. This confuses me because I clearly assigned it in the very first function: def midPoint, so any help with that problem would be great. Other than that I'm not sure why I can't get the actual triangle to show up. I just want to start out by getting it to show up black first, and then color it. Any help on what is wrong with my, most likely, awful code would be much appreciated. I am supposed to be minoring in computer science but I am failing the class. I am desperate. Please you can even make fun of my shitty code but I need something, anything. I'm lost. Thank you.
#######################################
import pygame, math, random
pygame.init()
#######################################
def midpoint (x0, x1, y0, y1):
panda = ((x0 + x1)/2)
polarbear = ((y0 + y1)/2)
return panda, polarbear
#######################################
def randomPoint (width, height):
potato = (random.random() * width)
pickle = (random.random() * height)
return potato, pickle
#newImage
# PRE: Takes size, which is a 2-tuple (width, height) and provides size of image
# POST: Creates list of length size[0]. Each item in list is length size[1]. Each item of list is a 3-tuple.
#
# Points of this data structure are denoted as image[x][y] and each point has 3 components: [0] for red, [1] for green, and [2] for blue
#
def newImage(size):
return pygame.surfarray.array3d(pygame.Surface(size))
#######################################
#showImage
# Takes image created by newImage and displays it in open window
# PRE: image is a list of lists of 3-tuples. Each 3 tuple corresponds to a (R,G,B) color.
# POST: image is displayed in window.
#
def showImage(image):
width, height, depth = image.shape
pygame.display.set_mode((width, height))
surface = pygame.display.get_surface()
pygame.surfarray.blit_array(surface, image)
pygame.display.flip()
#######################################
# MAIN PROGRAM
pygame.init()
width = int(input("How large do you want your window? "))
height = int(input("How tall do you want your window? "))
window = newImage((width, height))
for x in range(width):
for y in range(height):
window[x][y] = (255,255,255) #Colors window white
showImage(window)
#
p = randomPoint(width, height)
for i in range(1000001):
corners = [(width, height),(0, height),(width//2, 0)]
c = random.choice(corners)
mid = midPoint(p[0], p[1], c[0], c[1])
if i > 10:
window[(mid[0])][(mid[1])] = (0,0,0)
p = mid
if i % 1000 == 0:
showImage(window)
#
print('Done!')
input("Enter to quit")
pygame.quit()
#
#######################################`
###################SIERPINSKI TRIANGLE (R,G,B)###################
###########################
###################
##########
#######################################
import pygame, math, random
pygame.init()
#######################################
#newImage
# PRE: takes a 2-tuple (width, height) input from user and provides size of image
# POST: Creates list, len[0]. Each item in list is len[1]. Each item is 3-tuple.
# Points of data structure (pixels) are denoted as image[x][y] and each point has 3 components:
##########################################################################################
[0] - RED
##########################################################################################
[1] - GREEN
##########################################################################################
[2] - BLUE
def newImage(size):
return pygame.surfarray.array3d(pygame.Surface(size))
#######################################
#showImage
# Main function: Takes image created by "newImage" and displays it in pygame window
# PRE: image is a LIST OF LISTS of 3-tuples. Each 3 of the tuples corresponds to a (R,G,B) color.
# POST: image is displayed in window
def showImage(image):
width, height, depth = image.shape
pygame.display.set_mode((width, height))
surface = pygame.display.get_surface()
pygame.surfarray.blit_array(surface, image)
pygame.display.flip()
#######################################
#randomPoint
# set the variable "p" in main function to the returned values of this function which should be a random point
# PRE: takes in 2-tuple (width, height)
# POST: returns coordinates of a random point in the size of the image
def randomPoint(width, height):
ex = (random.random() * width)
wye = (random.random() * height)
return ex, wye
#######################################
#midPoint
# find the mid-point between "p" - random point and "c" - random corner in the main function
# PRE: take in all 4 coordinates of the 2 points
# POST: calculate the mid-point between these 2 points and color it black. Set p to the midPoint once this function is complete and repeat.
def midPoint(x0, y0, x1, y1):
eks = ((x0 + x1)/2)
wie = ((y0 + y1)/2)
return eks, wie
#######################################
def colorPoint (x, y, width, height):
w = (255/width)
h = (255/height)
x_color = x*w
y_color = y*h
r = math.fabs(255 - y_color)
g = math.fabs(255 - x_color)
b = math.fabs(255 - x_color - y_color)
return r, g, b
showImage(window)
#######################################
# MAIN PROGRAM
# Start the CHAOS GAME
pygame.init()
#######################################
# Get user input for the width and height of the window
width = int(input("How wide would you like your window to be? "))
height = int(input("How tall would you like your window to be? "))
window = newImage((width, height))
for ecks in range(width):
for why in range(height):
window[ecks][why] = (255,255,255) #Colors window white
showImage(window)
#######################################
# Set "p" to starting value
p = 1
# Call randomPoint in order to set "p" to a random point within the parameters of the window size
p = randomPoint(width, height)
i = 0
for i in range(1000001):
corners = [(width, height),(0, height),(width//2, 0)]
c = random.choice(corners)
mid = midPoint(p[0], p[1], c[0], c[1])
colour = colorPoint((mid[0]), (mid[1]), width, height)
if i > 10:
window[(mid[0])][(mid[1])] = colour
i += 1
p = mid
if i % 1000 == 0:
showImage(window)
#######################################
#End the CHAOS GAME
print ('Done!')
input ("ENTER to quit")
pygame.quit()
#######################################

Categories