Additive blending in pygame doesn't work with alpha - python

I recently discovered the different blending modes you can apply to blitted surfaces in pygame and I wanted to see how flexible the system was. Unless I'm doing something wrong, it's apparently pretty limited (just like the rest of pygame OOOOOOOH shots fired). I wrote a simple program that draws a bunch of gradient circles using alpha and blits them all around the screen. This is the code:
import pygame
import pygame.gfxdraw
pygame.init()
import random
SCREEN = pygame.display.set_mode((800, 600))
SCREEN.fill((0, 0, 0))
def draw_square(surface, colour, x, y):
"""
Yeah it's called draw square but it actually draws a circle thing I was just too lazy
to change the name so you gotta deal with it.
"""
square = pygame.Surface((100, 100))
square.fill((0, 0, 0))
colour += (int(15/255*100), )
for i in range(25):
pygame.gfxdraw.filled_circle(square, 50, 50, i*2, colour)
# Comment the previous two lines out and uncomment the next line to see different results.
# pygame.draw.circle(square, colour[:3], (50, 50), 50)
surface.blit(square, (x - 50, y - 50), special_flags=pygame.BLEND_RGB_ADD)
running = True
while running:
for evt in pygame.event.get():
if evt.type == pygame.QUIT:
running = False
draw_square(SCREEN, (25, 255, 25), random.randint(0, 800), random.randint(0, 600))
pygame.display.update()
pygame.quit()
It seems to work when drawing a normal circle, but when drawing the circles with pygame.gfxdraw.filled_circle additive blending doesn't work. Any ideas?
EDIT: I'm using Python 3, so 15/255 evaluates properly to a float.

The issue is still with this line:
colour += (int(15/255*100), )
It should go to white initially, but the alpha is so low it will take a long time (well, it should in theory...).
Doing:
colour += (int(125/255*100), )
Makes the effect more obvious.
Result:

Related

How do I make a circle semi-transparent in pygame?

I want to make a partially transparent circle as an area of effect indicator, but I haven't found any way to do this in pygame.
I tried passing in an rgba 4-tuple as an argument (as suggested by this post), but it didn't change the transparency at all. On top of that, according to this other post, the method shouldn't work at all, since draw doesn't accept alpha values. That's conflicting information.
pygame.draw.circle(screen, (255, 0, 0, 100), (x, y), r)
I've also tried creating a new surface, drawing the circle onto that surface, and then blitting the new surface onto my game screen (as suggested by this post). It gives me a semi-transparent circle as requested, but since surfaces in pygame are rectangular, it also gives me a gray rectangle around the circle, which I don't want.
s = pygame.Surface((1000, 1000))
s.set_alpha(100)
pygame.draw.circle(s, (255, 0, 0), (x, y), r)
screen.blit(s, (0, 0))
Is there any way to make this circle semi-transparent without adding any other visible effects to my screen?
Here's a runnable version of the answer to the last question you linked to titled How to draw a semi-transparent circle in Pygame?. It seems to do what you want, as far as I can tell.
import pygame
width, height = 640, 480
pygame.init()
clock = pygame.time.Clock()
screen = pygame.display.set_mode((width,height))
surface = pygame.display.set_mode((width,height), pygame.SRCALPHA)
while True:
msElapsed = clock.tick(100)
screen.fill((255,255,255))
pygame.draw.circle(surface,(30,224,33,100),(250,100),10)
screen.blit(surface, (0,0))
pygame.display.update()
for event in pygame.event.get():
if event.type==pygame.QUIT:
exit()

Rotating An Image In Pygame Without Distortion

I have been trying to make an image rotate in pygame, using python 3.6, however when I do it either distorts the image into an unrecognizable image, or when it rotates it bumps all over the place
Just using pygame.transform.rotate(image, angle) makes the distorted mess.
And using something like:
pygame.draw.rect(gameDisplay, self.color, [self.x, self.y, self.width, self.height]) makes the image bump all over the place.
I have looked at many questions on this site and others and so far none of them have worked perfectly.
To anyone who is interested here is the link to my code so far.
https://pastebin.com/UQJJFNTy
My image is 64x64.
Thanks in advance!
Per the docs (http://www.pygame.org/docs/ref/transform.html):
Some of the transforms are considered destructive. These means every time they are performed they lose pixel data. Common examples of this are resizing and rotating. For this reason, it is better to re-transform the original surface than to keep transforming an image multiple times.
Each time you call transform.rotate you need to do it on the original image, not on the previously rotated one. For example, if I want the image rotated 10 degrees each frame:
image = pygame.image.load("myimage.png").convert()
image_clean = image.copy()
rot = 0
Then in your game loop (or object's update):
rot += 10
image = pygame.transform.rotate(image_clean, rot)
Here's a complete example. Don't modify the original image and in the while loop use pygame.transform.rotate or rotozoom to get a new rotated surface and assign it to another name. Use a rect to keep the center.
import sys
import pygame as pg
pg.init()
screen = pg.display.set_mode((640, 480))
BG_COLOR = pg.Color('darkslategray')
# Here I just create an image with per-pixel alpha and draw
# some shapes on it so that we can better see the rotation effects.
ORIG_IMAGE = pg.Surface((240, 180), pg.SRCALPHA)
pg.draw.rect(ORIG_IMAGE, pg.Color('aquamarine3'), (80, 0, 80, 180))
pg.draw.rect(ORIG_IMAGE, pg.Color('gray16'), (60, 0, 120, 40))
pg.draw.circle(ORIG_IMAGE, pg.Color('gray16'), (120, 180), 50)
def main():
clock = pg.time.Clock()
# The rect where we'll blit the image.
rect = ORIG_IMAGE.get_rect(center=(300, 220))
angle = 0
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
# Increment the angle, then rotate the image.
angle += 2
# image = pg.transform.rotate(ORIG_IMAGE, angle) # rotate often looks ugly.
image = pg.transform.rotozoom(ORIG_IMAGE, angle, 1) # rotozoom is smoother.
# The center of the new rect is the center of the old rect.
rect = image.get_rect(center=rect.center)
screen.fill(BG_COLOR)
screen.blit(image, rect)
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
main()
pg.quit()
sys.exit()

How to rotate a surface in pygame, without changing its shape

I'm writing a class in pygame to create a sprite object, and I'd like to be able to rotate it. It works fine with an image, and rotates without issue. But when rotating a surface with a plain colour, the box appears to grow and shrink. I know that this is a result of the surface changing size to fit the vertices of the rectangle inside, but how do I stop it? I'd like to see a visual rotation.
I've created some sample code to show the problem that I'm facing, running it causes the box to simply change in size.
import sys, pygame
from pygame.locals import *
pygame.init()
SCREEN = pygame.display.set_mode((200, 200))
CLOCK = pygame.time.Clock()
surface = pygame.Surface((50 , 50))
surface.fill((0, 0, 0))
rotated_surface = surface
rect = surface.get_rect()
angle = 0
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
SCREEN.fill((255, 255, 255))
angle += 5
rotated_surface = pygame.transform.rotate(surface, angle)
rect = rotated_surface.get_rect(center = (100, 100))
SCREEN.blit(rotated_surface, (rect.x, rect.y))
pygame.display.update()
CLOCK.tick(30)
How do I fix this issue, to make the surface rotate how I want?
Any help would be appreciated!
You have to create the Surface objects you create to stamp on the display surface in a way they use transparency information (That is - they have to have an alpha channel).
To do that, is just a question of passing the appropriate flag when creating your surface objects - simply replace this:
surface = pygame.Surface((50 , 50))
with:
surface = pygame.Surface((50 , 50), pygame.SRCALPHA)
and it should work.

pygame - trying to move a rectangle

I am codding a drum game (based on dtx-Mania).
I got an issue on how to draw a rectangle and move it. I can make a static one, but not a moving one. I currently used a line but it seams pygame draw it as a rectangle so far if it as an impact i will change back to rectangle.
My goal is to draw the rectangle and move it at a slow enough pace so that it takes about 1 second to get to a line.
I know I still have a lot to learn, this is what I got to test so far.
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
#small exemple of a moving rectangle
import pygame, sys
pygame.init()
fpsClock = pygame.time.Clock()
windowsSurfaceObj = pygame.display.set_mode((640, 480))
pygame.display.set_caption('moving rectangle test')
white = pygame.Color(255, 255, 255)
black = pygame.Color(0, 0, 0)
step = pygame.draw.line(windowsSurfaceObj, white, (233, 0), (269, 0), 6)
step
while True:
windowsSurfaceObj.fill(black)
#the coordonate are moved but the rectangle is now drew
step.move(0, -1)
#this is the target line (where the moving object must go to)
pygame.draw.line(windowsSurfaceObj, white, (90, 420), (390, 420), 6)
pygame.display.update()
fpsClock.tick(30)
Thank you for your help.
It is always good to read the docs. From the pygame docs:
line(Surface, color, start_pos, end_pos, width=1) -> Rect
Draw a straight line segment on a Surface. There are no endcaps, the
ends are squared off for thick lines.
In the description text of the whole module:
The functions return a rectangle representing the bounding area of changed pixels.
So you draw your first line, and move a rectangle representing a bounding are of changed pixels. Since you do not redraw your line, your first line disappears.
To solve this, you need to draw the line in the while loop after moving.
step.move(0, -1)
pygame.draw.rect(windowsSurfaceObj, white,step, 6)

How to draw to an off-screen display in PyGame

I'm doing a graphics test using PyGame to simulate the Dragon Curve being unfolded. I already made one successful version that keeps track of all the points as they rotate around each other, but obviously, this begins to slow down pretty substantially after a few iterations. In order to speed this up, I want to simply store the drawn segments into an image variable, and continually save a segment of the screen to a variable and draw those moving rather than keeping track of a lot of points. How can I do either of the following?
Draw to an off-screen image variable that then gets drawn to the screen in the correct place
Save a section of the visible display into an image variable
I tried reading through some of the PyGame documentation, but I didn't have any success.
Thanks!
Creating an additional surface object, and drawing to it is the solution. This surface object can then be drawn onto the display's surface object as shown below.
More information on the PyGame Surface object can be found here
import pygame, sys
SCREEN_SIZE = (600, 400)
BG_COLOR = (0, 0, 0)
LINE_COLOR = (0, 255, 0)
pygame.init()
clock = pygame.time.Clock() # to keep the framerate down
image1 = pygame.Surface((50, 50))
image2 = pygame.Surface((50, 50))
image1.set_colorkey((0, 0, 0)) # The default background color is black
image2.set_colorkey((0, 0, 0)) # and I want drawings with transparency
screen = pygame.display.set_mode(SCREEN_SIZE, 0, 32)
screen.fill(BG_COLOR)
# Draw to two different images off-screen
pygame.draw.line(image1, LINE_COLOR, (0, 0), (49, 49))
pygame.draw.line(image2, LINE_COLOR, (49, 0), (0, 49))
# Optimize the images after they're drawn
image1.convert()
image2.convert()
# Get the area in the middle of the visible screen where our images would fit
draw_area = image1.get_rect().move(SCREEN_SIZE[0] / 2 - 25,
SCREEN_SIZE[1] / 2 - 25)
# Draw our two off-screen images to the visible screen
screen.blit(image1, draw_area)
screen.blit(image2, draw_area)
# Display changes to the visible screen
pygame.display.flip()
# Keep the window from closing as soon as it's finished drawing
# Close the window gracefully upon hitting the close button
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit(0)
clock.tick(30)

Categories