In pygame, when I draw a rectangle, corners are not filled like I want them to be. It looks like:
The problem is the same even with thick lines so I don't think it is the problem.
In another drawing library there were line styles like square or round. I searched everywhere and I couldn't find something similar. Anyone knows how this can be done without filling the corners manually?
import pygame
pygame.init()
while True:
window = pygame.display.set_mode((500, 500))
pygame.draw.rect(window, (255, 255, 255), (100, 100, 100, 100), 10)
pygame.display.flip()
draw.rect is really just a shortcut for draw.polygon using the four corners of the Rect. For each edge of the polygon, it draws a line of the specified width from one vertex to the next. It doesn't go beyond the vertex when you increase the line width. This will make it look like there are gaps in the corners for widths > 1. If you notice there are no gaps for widths of 0 and 1. Also notice that when increasing the line width, the lines grow on both sides (inside and outside of the specified rect). In fact if you increase your width to half the size of the rect, you get something that looks like a cross. If you are wanting better thick rectangles where you can specify the exact inside and outside, I'd use Surface.fill() for the outer Rect and the inside Rect.
Add example of Surface.fill()
window.fill((255, 255, 255), Rect(100, 100, 100, 100)) # outer rect
window.fill((0, 0, 0), Rect(110, 110, 80, 80)) # inner rect
Related
In a pygame project I'm working on, sprites of characters and objects cast a shadow onto the terrain. Both the shadow and the terrain are normal pygame surfaces so, to show them, the shadow is blitted onto the terrain. When there's no other shadow (only one shadow and the terrain) everything works fine, but when the character walks into the area of a shadow, while casting its own shadow, both shadows combine their alpha values, obscuring the terrain even more.
What I want is to avoid this behaviour, keeping the alpha value stable. Is there any way to do it?
EDIT: This is an image, that I made in Photoshop, to show the issue
EDIT2: #sloth's answer is ok, but I neglected to comment that my project is more complicated than that. The shadows are not whole squares, but more akin to “stencils”. Like real shadows, they are silhouettes of the objects they are cast from, and therefore they need per pixel alphas which are not compatible with colorkey and whole alpha values.
Here is a YouTube video that shows the issue a bit more clearly.
An easy way to solve this is to blit your shadows on another Surface first which has an alpha value, but no per pixel alpha. Then blit that Surface to your screen instead.
Here's a simple example showing the result:
from pygame import *
import pygame
pygame.init()
screen = pygame.display.set_mode((800, 600))
# we create two "shadow" surfaces, a.k.a. black with alpha channel set to something
# we use these to illustrate the problem
shadow = pygame.Surface((128, 128), pygame.SRCALPHA)
shadow.fill((0, 0, 0, 100))
shadow2 = shadow.copy()
# a helper surface we use later for the fixed shadows
shadow_surf = pygame.Surface((800, 600))
# we set a colorkey to easily make this surface transparent
colorkey_color = (2,3,4)
shadow_surf.set_colorkey(colorkey_color)
# the alpha value of our shadow
shadow_surf.set_alpha(100)
# just something to see the shadow effect
test_surface = pygame.Surface((800, 100))
test_surface.fill(pygame.Color('cyan'))
running = True
while running:
for e in pygame.event.get():
if e.type == pygame.QUIT:
running = False
screen.fill(pygame.Color('white'))
screen.blit(test_surface, (0, 150))
# first we blit the alpha channel shadows directly to the screen
screen.blit(shadow, (100, 100))
screen.blit(shadow2, (164, 164))
# here we draw the shadows to the helper surface first
# since the helper surface has no per-pixel alpha, the shadows
# will be fully black, but the alpha value for the full Surface image
# is set to 100, so we still have transparent shadows
shadow_surf.fill(colorkey_color)
shadow_surf.blit(shadow, (100, 100))
shadow_surf.blit(shadow2, (164, 164))
screen.blit(shadow_surf, (400, 0))
pygame.display.update()
You could create a function that tests for shadow collision and adjust the blend values of the shadows accordingly.
You can combine per-pixel alpha shadows by blitting them onto a helper surface and then fill this surface with a transparent white and pass the pygame.BLEND_RGBA_MIN flag as the special_flags argument. The alpha value of the fill color should be equal or lower than the alphas of the shadows. Passing the pygame.BLEND_RGBA_MIN flag means that for each pixel the lower value of each color channel will be taken, so it will reduce the increased alpha of the overlapping shadows to the fill color alpha.
import pygame as pg
pg.init()
screen = pg.display.set_mode((800, 600))
clock = pg.time.Clock()
shadow = pg.image.load('shadow.png').convert_alpha()
# Shadows will be blitted onto this surface.
shadow_surf = pg.Surface((800, 600), pg.SRCALPHA)
running = True
while running:
for event in pg.event.get():
if event.type == pg.QUIT:
running = False
screen.fill((130, 130, 130))
screen.blit(shadow, (100, 100))
screen.blit(shadow, (154, 154))
shadow_surf.fill((0, 0, 0, 0)) # Clear the shadow_surf each frame.
shadow_surf.blit(shadow, (100, 100))
shadow_surf.blit(shadow, (154, 154))
# Now adjust the alpha values of each pixel by filling the `shadow_surf` with a
# transparent white and passing the pygame.BLEND_RGBA_MIN flag. This will take
# the lower value of each channel, therefore the alpha should be lower than
# the shadow alphas.
shadow_surf.fill((255, 255, 255, 120), special_flags=pg.BLEND_RGBA_MIN)
# Finally, blit the shadow_surf onto the screen.
screen.blit(shadow_surf, (300, 0))
pg.display.update()
clock.tick(60)
Here's the shadow.png.
Alternatively, how does one fill a polygon in Pygame with an image?
I've tried searching documentation but didn't turn up much, so I'm asking here to see if I missed out anything. I can use transformation matrices if needed.
Here's an example of what I want to achieve:
You might be looking for pygame's alternative drawing functions from pygame.gfxdraw, what your looking is textured polygon
Note: You need to import it separately as gfxdraw doesn't import as default so you need to import it separately e.g.
import pygame
import pygame.gfxdraw
I think the concept you are looking for is a bitmap mask. Using a special blending mode, pygame.BLEND_RGBA_MULT, we can blend the contents of two surfaces together. Here, by blending, I mean that we can simply multiply the color values of two corresponding pixels together to get the new color value. This multiplication is done on normalized RGB colors, so white (which is (255, 255, 255) would actually be converted to (1.0, 1.0, 1.0), then multiplication between the masking surface and the masked surface's pixels would be performed, and then you would convert back to the non-normalized RGB colors.
In this case, all that means is that we need to draw the polygon to one surface, draw the image of interest to another surface, and blend the two together and render the final result to the screen. Here is the code that does just that:
import pygame
import sys
(width, height) = (800, 600)
pygame.init()
screen = pygame.display.set_mode((width, height))
image = pygame.image.load("test.png").convert_alpha()
masked_result = image.copy()
white_color = (255, 255, 255)
polygon = [(0, 0), (800, 600), (0, 600)]
mask_surface = pygame.Surface((width, height))
pygame.draw.polygon(mask_surface, white_color, polygon)
pygame.draw.aalines(mask_surface, white_color, True, polygon)#moderately helps with ugly aliasing
masked_result.blit(mask_surface, (0, 0), None, pygame.BLEND_RGBA_MULT)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
screen.blit(masked_result, (0, 0))
pygame.display.update()
The result looks like this using a simple triangle as the mask.
Some things to note:
This answer is based off of this, which you might be interested in if you want more complicated masks than polygons (such as pretty bitmap masks).
This solution is not using the pygame.mask module!
I have not fully tested this code yet, but I suspect that this will only work for convex polygons, which may be a significant limitation. I am not sure if pygame can draw concave polygons, but I know PyOpenGL can using stencil buffer tricks or tesselation. Let me know if you are interested in that approach instead.
While I have attempted to get rid of aliasing, the results are still not completely satisfactory.
I am working on a graphing program that I am calling PyGraph.
It allows you to create a graph of any size and draw on it, and later in development I will provide coordinates and things, but for now I have one question: How can I draw a intersecting lines through the center to represent the origin?
Here is what I have so far:
#pygraph
import pygame
from pygame.locals import *
pygame.init()
screen=pygame.display.set_mode((640,480))
x=0
y=0
size=16
screen.fill((255,255,255))
pygame.draw.line(screen, (0,0,0), (screen.get_width()/2,0),(screen.get_width()/2,screen.get_height()),5)
pygame.draw.line(screen, (0,0,0), (0,screen.get_height()/2),(screen.get_width(),screen.get_height()/2),5)
while True:
while y<480:
pygame.draw.rect(screen,(0,0,0),(x,y,size,size),1)
if x>640:
x=0
y+=size
pygame.draw.rect(screen,(0,0,0),(x,y,size,size),1)
x+=size
for e in pygame.event.get():
if e.type==QUIT:
exit()
if e.type==KEYUP:
if e.key==K_SPACE:
x=0
y=0
screen.fill((255,255,255))
pygame.draw.line(screen, (0,0,0), (screen.get_width()/2,0),(screen.get_width()/2,screen.get_height()),5)
pygame.draw.line(screen, (0,0,0), (0,screen.get_height()/2),(screen.get_width(),screen.get_height()/2),5)
size=input('Enter size: ')
pygame.display.flip()
The lines go though the center, but it doesn't work for every size graph. I'm not the best at math, but I hope this isn't obvious.. any advice?
The problem is that you draw the grid using the top left corner as your anchor. That is, all your grid rectangles have one corner in the top left. This becomes a problem when the distance between the center line and the screen edge is not divisible by the size - you can't divide a line of 640 units into even divisions of 15, for example.
A far better solution would be to use the center as the anchor. So basically, all the grid rectangles have one corner in the center of the graph, which means you will never get any "remainder" on the center line, and the "remainder" will instead be on the border of the graph, which looks much nicer.
Here is code for anchoring your rectangles at the center (should replace your original while y<480 loop):
while y<=480/2+size:
pygame.draw.rect(screen,(0,0,0),(640/2+x, 480/2+y,size,size),1)
pygame.draw.rect(screen,(0,0,0),(640/2-x, 480/2+y,size,size),1)
pygame.draw.rect(screen,(0,0,0),(640/2+x, 480/2-y,size,size),1)
pygame.draw.rect(screen,(0,0,0),(640/2-x, 480/2-y,size,size),1)
x+=size
if x>=640/2+size:
x=0
y+=size
Brief explanation:
I change the anchor of the rectangle (the point you pass into pygame.draw.rect) to the center of the graph, and instead of drawing one rectangle, I draw four - one in each quadrant of the graph.
I also fixed the code a bit to not need to call pygame.draw.rect() in the if statement.
A minor style tip:
Replace 480 and 640 with "screen.width" and "screen.height", so you can adjust the width and height later without problems.
with the current code, this is the result.
but I'm trying to get it to look like this, but not sure what to tweak.
#circle circle
import turtle
turtle.colormode(255)
window=turtle.Screen()
draw=turtle.Turtle()
draw.pensize(2)
window.screensize(1200,1200)
draw.speed('fastest')
red, green, blue= 255, 255, 0
for shape in range(30):
for circle in range(1):
draw.circle(200)
draw.penup()
draw.forward(30)
draw.pendown()
draw.left(20)
green=green-5
blue=blue+6
draw.color(red, green, blue)
window.mainloop()
If you notice your two images, the one you want has the circles drawn outside the center, while the wrong one draws them around the center. Try changing draw.left(20) to draw.right(20) and adjust your values from there to get the sizes you want.
The change keeps the turtle outside of the circle just drawn, which is what you're looking for.
I have an application written in python that's basically an etch-a-sketch, you move pixels around with WASD and arrow keys and it leaves a trail. However, I want to add a counter for the amount of pixels on the screen. How do I have the counter update without updating the entire surface and pwning the pixel drawings?
Alternatively, can I make a surface that's completely transparent except for the text so you can see the drawing surface underneath?
To solve this problem, you want to have a separate surface for your Etch-a-Sketch pixels, so that they do not get clobbered when you go to refresh the screen. Unfortunately, with Rigo's scheme, the font will continue to render on top of itself, which will get messy for more than two pixel count changes.
So, here's some sample rendering code:
# Fill background
screen.fill((0xcc, 0xcc, 0xcc))
# Blit Etch-a-Sketch surface (with the drawing)
# etch_surf should be the same size as the screen
screen.blit(etch_surf, (0, 0))
# Render the pixel count
arial = pygame.font.SysFont('Arial', 20)
counter_surf = arial.render(str(pixel_count), True, (0, 0, 0))
screen.blit(counter_surf, (16, 16))
# Refresh entire screen
pygame.display.update()
Now, admittedly, updating the entire screen is rather inefficient. For this, you have two options: only refresh the screen when the drawing changes or track the location of drawing changes and refresh individual locations (see the update documentation). If you choose the second option, you will have to refresh the text and where it was previously; I would recommend having a Sprite manage this.
What you need is pygame.font module
#define a font surface
spamSurface = pygame.font.SysFont('Arial', 20)
#then, in your infinite cycle...
eggsPixels = spamSurface.render(str(pixelsOnScreen), True, (255, 255, 255))
hamDisplay.blit(eggsPixels, (10, 10))
Where spamSurface is a new font surface, eggsPixels is the value that spamSurface will render (display/show) and hamDisplay is your main surface display.