Artifacts when drawing primitives with pygame? - python

I'm working on some code involving use of the pygame library to draw a circle to a surface. The code to draw the circle looks something like the following:
surf = pygame.Surface((2000,2000))
pygame.draw.circle(surf,
pygame.Color("white"),
(1000,1000),
508,
50)
The problem is that the resulting circle has artifacts
Is there anyway to draw a circle like this without getting these artifacts?

What you see is most likely a problem with pygame.draw.circle implementation. Most likely pygame developer foolishly assumed that you can draw thick circle by drawing several circles in sequence, increasing radius by one for every circle. That's not going to work in practice and you'll get moire pattern like the one you see. Try increasing circle line thickness by 100 or 200 and see what happens. If it gets worse, then it is pygame.draw.circle's fault.
A solution would be to provide your own circle rendering routine. This can be certainly be done, but for optimum performance it makes sense to do it in C/C++ extension module for pygame OR using OpenGL (with or without fragment shaders).
To get a perfect circle for every scanline you'll have to calculate start and end of filled area for every scanline. It might be possible to do this in python with tolerable performance by using pygame.draw.line to draw individual scanlines.

This bug is mentioned in the comments for the circle and arc methods. There's one workaround in the comments of the draw.circle documentation. This is adapted from that workaround:
def draw_outlined_circle(surf, color, origin, radius, thickness):
width = radius * 2 + thickness * 2
background = (0, 0, 0, 0)
circle = pygame.Surface((width, width)).convert_alpha()
rect = circle.get_rect()
circle.fill(background)
pygame.draw.circle(circle, color, rect.center, radius)
pygame.draw.circle(circle, background, rect.center, radius - thickness)
surf.blit(circle, (origin[0] - (rect.w / 2), origin[1] - (rect.w / 2)))
This method uses an extra Surface and draws two circles: a smaller circle inside of a larger one to achieve the same effect. The smaller circle's fill color has an alpha of 0, so the full circle's center appears transparent.
EDIT: From the above comment, I see the behavior is mentioned in the pygame FAQ as well.

I fixed this issue, and it will be in the next release of pygame (1.9.4). How do you draw circles like that without artifacts? Use the newer version of pygame.

This might have happened with a problem on how pygame.draw.circle is implementing.
You can try the arcade library for better performance. Also, the arcade library is very easy to use while I find pygame quite complicated.

Related

Procedural GL textures

Background
I'm working in python/pyglet to create a procedurally generated terrain.
I'm visualizing it with pyglet by running a lambda function for each x and y coordinate on the screen and then painting that pixel in the color I want it.
This is not very optimal.
I have a sneaking suspicion that there is a way to create a rectangular object and tell the GL engine to render a texture upon it, giving the GL engine a lambda function that will return the color in exchange for an x and y coordinate.
Pseudo pyglet example
def get_color(pos):
x, y = pos
color = x * y % 255
return [color, color, color]
width = 1680
height = 1024
win = window.Window(fullscreen=False, vsync=True, width=width, height=height)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluOrtho2D(0, win.width, 0, win.height)
glTextureMapper(get_color)
win.clear()
points = [0, 0, width, 0, width, height, 0, height]
pyglet.graphics.draw(int(len(points) / 2), GL_QUADS, ('v2f', points))
TLDR Question
How do I color a polygon using a lambda function? Is this a shader?
Pyglet examples are preferred, but examples in any language are welcome
This is probably not a complete answer, but might get you a bit further.
Also keep in mind that I've never worked much with textures at all weirdly enough.
What you might be able to do, is create a texture-group for any set of quads, and add them into a batch. Because I'm not entirely sure why you need a lambda function?
class TextureGroup(pyglet.graphics.Group):
def set_state(self):
glEnable(texture.target)
glBindTexture(texture.target, texture.id)
def unset_state(self):
glDisable(texture.target)
vertice_mem = {}
batch = pyglet.graphics.Batch()
texture_group = TextureGroup()
vertice_list = batch.add(2, pyglet.gl.GL_QUADS, None, ('v2f', points))
vertice_mem[(x,y)] = vertex_list
This is one way to optimize how textures are added to certain faces and also add those faces into a batch rendering them a lot faster and more dynamic.
If you need to update the vertices, you can always access the vertice_list.vertices and change the x,y pair for that particular face.
You can use the custom group to create certain textures "on the fly", or on this case pre-rendered certain textures which you could stitch together to create "procedural" textures.
What #Ripi2 says is also a good tip, read up on how procedural memory mapping works and what it actually is. Most graphics you see in sand-box games are actual clever re-use of textures to create the illusion of on-demand graphics.
The last and final tip is that you will most likely need to look into shaders, they are by far the fastest way to auto-generate "textures" by manipulating and creating turbulence in textures, else will be heavily taxing on your system because you will need to iterate over the pixel area in some way.
But again, this is not my area of experience - only a fellow travelers bits and pieces of information gathered over the years.
As pointed out strictly speaking OpenGL cannot draw rectangles (quads), but you can always use a diagonal between two corners to make two triangles. (There can be some differences in interpolation between corners, but they probably won't affect you.)
And while I'm being pedantic, you're not coloring pixels, you're colouring points on your 3D terrain object surface. (I'm assuming you want the terrain to keep the same colors when viewed from any angle or distance.)
OK, what I hope will be the real answer. Is your procedural terrain generation coloring done once, or does it change dynamically?
If you only create and color the terrain once, use a texture map. You'll need to learn how to assign texture coordinates at the corners of the terrain (TL;DR: they'll be (0.0,0.0), (1.0,0.0), (1.0,1.0), (0.0, 1.0) ) and how to use glTexImage2D to transfer an image to the GPU.
Texture maps are just 2D raster images. Pyglet should be able to handle this for you, if not either Pillow or the Python Imaging Library packages.
If you want the terrain to change colors on the fly, you need a fragment shader, a small function written in OpenGL Shading Language that executes every time the terrain is drawn. And due to the way OpenGL works you'll also have to write a vertex shader that handles the 3D coordinate transformations.
This will mean more thinking and coding that the texture map approach. But everything in OpenGL (or DirectX, or RenderMan, …) is done with shaders these days, so you might as well start learning. And shaders let you do any kind of procedural modelling and animation, not just colors. For example, you could dynamically generate your terrain heights as well as colors.
Hope this helps.

Center of Rotation, Pygame

I recently just asked this question. I got my answer the only problem is the rotation is weird. It does not have one center point. The best way I can describe the rotation is the image pushed up to some left invisible wall, climbs up and then rotates until so other edge hits the invisible wall. I believe I need to set a constant center. How do I do this and where in my code do I put the center? I've looked at many examples but it seems like this weird rotation keeps occurring. Here is my code:
def update_angle(self):
orig_car = self.car1
loc = orig_car.get_rect().center
car2 = pygame.transform.rotate(orig_car, self.angle)
car2.get_rect().center = loc
return car2
I had a similar problem to you, I managed to pull a small amount of code together to fix it. When you're blitting the car to the screen use this code:
rect = car2.get_rect(center=(200,200)) #Set the centre of rotation
SCREEN.blit(car2, rect)
It allows the image to rotate about a point, hope this helps!

Changing the Origin of a Animation in Python and Pygame

I am currently working on a 2D platformer and the sprites that I have animate from the bottom left point of the animation and when I draw the animation using a x and y point it still animates from the bottom left, so when I draw the animation to the screen the sprite should get shorter but the sprites feet just lift up of the ground like this https://www.dropbox.com/s/ofeggmlcp4f6qsk/Animation_probs_video.mp4
I know the video is not high quality but so what.
His head should go up and down not his feet. If you guy's can help me I would be most greatful. I could also use a program that fixes that I have a Linux computer with a windows xp virtual box and I am using python 2.7 and pygame.
Thanks.
Assuming you are animating a series of rectangular sprites each being an instance of pygame.Surface, you will be adding the difference between the surface with the greatest height and the current sprite's surface to the y position every time you blit.
Find the height of the tallest sprite only once:
max_height = tallest_sprite.get_height()
Now while you are cycling through your sprints each frame with current_sprite:
screen.blit(current_sprite, (x, y+(max_height - current_sprite.get_height())
If framerate is an issue, you may want to calculate these differences beforehand and associate them with each sprite so you have one less get_height() call per frame.

A bit confused with blitting (Pygame)

I've just started learning some pygame (quite new to programming overall), and I have some very basic questions about how it works.
I haven't found a place yet that explains when I need to blit or not to include a certain surface on the screen. For example, when drawing a circle:
circle = pygame.draw.circle(screen, (0, 0, 0), (100, 100), 15, 1)
I don't need to do screen.blit(circle), but when displaying text:
text = font.render("TEXT", 1, (10, 10, 10))
textpos = text.get_rect()
textpos.centerx = screen.get_rect().centerx
screen.blit(text, textpos)
If I don't blit, the text won't appear.
To be honest, I really don't know what blitting is supposed to do, apart from "pasting" the desired surface onto the screen. I hope I have been clear enough.
The short answer
I haven't found a place yet that explains when I need to blit or not to include a certain surface on the screen.
Each operation will behave differently, and you'll need to read the documentation for the function you're working with.
The long answer
What Is Blitting?
First, you need to realize what blitting is doing. Your screen is just a collection of pixels, and blitting is doing a complete copy of one set of pixels onto another. For example, you can have a surface with an image that you loaded from the hard drive, and can display it multiple times on the screen in different positions by blitting that surface on top of the screen surface multiple times.
So, you often have code like this...
my_image = load_my_image()
screen.blit(my_image, position)
screen.blit(my_image, another_position)
In two lines of code, we copied a ton of pixels from the source surface (my_image) onto the screen by "blitting".
How do the pygame.draw.* functions blit?
Technically, the pygame.draw.* methods could have been written to do something similar. So, instead of your example...
pygame.draw.circle(screen, COLOR, POS, RADIUS, WIDTH)
...they COULD have had you do this...
circle_surface = pygame.draw.circle(COLOR, RADIUS, WIDTH)
screen.blit(circle_surface, POS)
If this were the case, you would get the same result. Internally, though, the pygame.draw.circle() method directly manipulates the surface you pass to it rather than create a new surface. This might have been chosen as the way to do things because they could have it run faster or with less memory than creating a new surface.
So which do I do?
So, to your question of "when to blit" and "when not to", basically, you need to read the documentation to see what the function actually does.
Here is the pygame.draw.circle() docs:
pygame.draw.circle():
draw a circle around a point
circle(Surface, color, pos, radius, width=0) -> Rect
Draws a circular shape on the Surface. The pos argument is the center of the circle, and radius is the size. The width argument is the thickness to draw the outer edge. If width is zero then the circle will be filled.
Note that it says that "draws a shape on the surface", so it has already done the pixel changes for you. Also, it doesn't return a surface (it returns a Rect, but that just tells you where the pixel changes were done).
Now let's look at the pygame.font.Font.render() documentation:
draw text on a new Surface
render(text, antialias, color, background=None) -> Surface
This creates a new Surface with the specified text rendered on it. Pygame provides no way to directly draw text on an existing Surface: instead you must use Font.render() to create an image (Surface) of the text, then blit this image onto another Surface.
...
As you can see, it specifically says that the text is drawn on a NEW Surface, which is created and returned to you. This surface is NOT your screen's surface (it can't be, you didn't even tell the render() function what your screen's surface is). That's a pretty good indication that you will need to actually blit this surface to the screen.
Blit means 'BL'ock 'I'mage 'T'ranfser
When you are displaying things on the screen you will, in some way, use screen because that's where you are putting it.
When you do:
pygame.draw.circle(screen, (0, 0, 0), (100, 100), 15, 1)
you are still using screen but you are just not blitting because pygame is drawing it for you.
And when you use text, pygame renders it into an image then you have to blit it.
So basically you blit images, but you can also have pygame draw them for you. But remember when you blit an image, say over a background, you need to loop it back and fourth; so that it blits the background, then the image, then the background etc...
You dont need to know much more than that, but you can read all about it here Pygame Blit
I hope this helped. Good Luck!
Imagine that you are a painter:
You have a canvas, and a brush.
Let's say that your main screen surface will be your canvas, and all the other surfaces, are "in your head" - you know how to draw them already.
When you call blit, you paint on top of the surface, covering any pixels that were overlapped. That is why you need to repaint the whole screen black so that you won't have any smudges on the painting while moving an object.
As Mark already said, you can draw a circle with a function, or first blit it to a new surface, and blit that on the screen surface.
If you have a more complicated surface - curves, text etc. you wouldn't need to have a surface for that, so you don't have to do any expensive calculations, just drawing. The setback is that your program takes up more memory, so you have to choose between those 2.

How do I clear a pygame alpha layer efficiently?

I'm trying to write a 2D game using python / pygame that blits several layers on top of one another every screen refresh. My basic setup is (from bottom to top):
Background: surface (non-transparent), scrolls at a different rate than rest
Midground: SRCALPHA transparent surface (static)
Player / Sprites / Enemies: sprite group
Forground: SRCALPHA transparent surface (static)
Right now, I'm blitting these four layers one on top of another every screen. The background scrolls at a different rate than the other three layers, which is why I have it separate from midground. As I have the game structured now, it runs on my fairly modest laptop at 60fps.
-BUT- I'm having trouble with the sprite group, which I'm blitting directly to the screen. Having to adjust the rect for every sprite according to my current viewport seems like an ugly way to program things, and I'd like a more elegant solution.
I'd love to blit the sprites to another transparent surface which I could manage, but therin lies my problem: I can't find a way of clearing a transparent layer that doesn't half my performance. Some of the setups I've tried:
I've tried filling the layer with a white surface with blend mode rgba_sub (surf.fill((255,255,255,255), area, BLEND_RGBA_SUB)) -- this is super, super slow
I've tried surface.copy() of a blank surface - this is faster, but still halves my fps
I've tried combining the sprites with the midground layer and using pygame.sprite.LayeredUpdates to update the sprites. This has no effect on performance, but does not work where the midground is transparent. I get trails of sprites over the background layer.
The best solution I've found so far is my current setup of drawing sprites directly to the screen. It looks great, runs fast, but is a pain to manage, as I have to make sure each sprites' rect is adjusted according to the viewport every frame. Its also making collision detection difficult.
Is there another quick way to clear a pygame transparent surface? Quick as in, can be done 60+ times a second? Alternately, is there a setup for my layers that would still accomplish the same effect?
I figured out a fast way of clearing a sprites only transparent layer by applying Peter's solution selectively to the layer:
for s in self.level.sprites:
spritelayer.fill((0), s.rect)
This seems to be working fine (erasing everything each frame) and still runs at 60fps.
The Surface.fill() will clear all R, G, B, and A values.
>>> img = pygame.image.load("hasalpha.png")
>>> print img.get_at((300, 300))
(238, 240, 239, 255)
>>> surface.fill(0)
>>> print img.get_at((300, 300))
(0, 0, 0, 0)
It sounds like this will do what you are describing. If you are trying to do something more specific with the alpha values the pygame.surfarray.pixel functions can give you directly editable values. That will be quick, but requires numpy as a dependency.

Categories