Texturing drawn shapes - python pygame - python

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()

Related

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

How to show an Image with pillow and update it?

I want to show an image recreated from an img-vector, everything fine.
now I edit the Vector and want to show the new image, and that multiple times per second.
My actual code open tons of windows, with the new picture in it.
loop
{
rearr0 = generateNewImageVector()
reimg0 = Image.fromarray(rearr0, 'RGB')
reimg0.show()
}
What can I do to create just one Window and always show just the new image?
Another way of doing this is to take advantage of OpenCV's imshow() function which will display a numpy image in a window and redraw it each time you update the data.
Note that OpenCV is quite a beast of an installation, but maybe you use it already. So, the code is miles simpler than my pygame-based answer, but the installation of OpenCV could take many hours/days...
#!/usr/local/bin/python3
import numpy as np
import cv2
def sin2d(x,y):
"""2-d sine function to plot"""
return np.sin(x) + np.cos(y)
def getFrame():
"""Generate next frame of simulation as numpy array"""
# Create data on first call only
if getFrame.z is None:
xx, yy = np.meshgrid(np.linspace(0,2*np.pi,w), np.linspace(0,2*np.pi,h))
getFrame.z = sin2d(xx, yy)
getFrame.z = cv2.normalize(getFrame.z,None,alpha=0,beta=1,norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_32F)
# Just roll data for subsequent calls
getFrame.z = np.roll(getFrame.z,(1,2),(0,1))
return getFrame.z
# Frame size
w, h = 640, 480
getFrame.z = None
while True:
# Get a numpy array to display from the simulation
npimage=getFrame()
cv2.imshow('image',npimage)
cv2.waitKey(1)
That looks like this:
It is dead smooth and has no "banding" effects in real life, but there is a 2MB limit on StackOverflow, so I had to decrease the quality and frame rate to keep the size down.
You can do that pretty simply and pretty fast with pygame.
You write a function called getFrame() that returns a numpy array containing an image that is calculated by your simulation.
By way of example, I create a 2-d sine wave on the first pass then roll that 1 pixel down and 2 pixels across on subsequent calls to simulate movement.
#!/usr/local/bin/python3
import numpy as np
import pygame
h,w=480,640
border=50
N=0
getFrame.z = None
def sin2d(x,y):
"""2-d sine function to plot"""
return np.sin(x) + np.cos(y)
def getFrame():
"""Generate next frame of simulation as numpy array"""
# Create data on first call only
if getFrame.z is None:
xx, yy = np.meshgrid(np.linspace(0,2*np.pi,h), np.linspace(0,2*np.pi,w))
getFrame.z = sin2d(xx, yy)
getFrame.z = 255*getFrame.z/getFrame.z.max()
# Just roll data for subsequent calls
getFrame.z = np.roll(getFrame.z,(1,2),(0,1))
return getFrame.z
pygame.init()
screen = pygame.display.set_mode((w+(2*border), h+(2*border)))
pygame.display.set_caption("Serious Work - not games")
done = False
clock = pygame.time.Clock()
# Get a font for rendering the frame number
basicfont = pygame.font.SysFont(None, 32)
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
# Clear screen to white before drawing
screen.fill((255, 255, 255))
# Get a numpy array to display from the simulation
npimage=getFrame()
# Convert to a surface and splat onto screen offset by border width and height
surface = pygame.surfarray.make_surface(npimage)
screen.blit(surface, (border, border))
# Display and update frame counter
text = basicfont.render('Frame: ' + str(N), True, (255, 0, 0), (255, 255, 255))
screen.blit(text, (border,h+border))
N = N + 1
pygame.display.flip()
clock.tick(60)
That looks like this. In real life it is very fast and very smooth, but there is a 2MB size limit for videos on StackOverflow, so I have generated a GIF with a low-ish frame rate and small-ish size just to keep it under 2MB.
Obviously you can add in detection of Up and Down arrows to speed up or slow down the animation, and you could detect left/right arrow and Spacebar to go backwards/forwards or pause the animation.
According to Pillow documentation the method Image.show() is mainly intended for debugging purposes. On Windows, it saves the image to a temporary BMP file, and uses the standard BMP display utility to show it.
You should take a look at the matplotlib image library.
You can try:
import matplotlib.pyplot as plt
imgplot = plt.imshow(rearr0) # "plot" the image.
For each iteration, you can call imgplot.clf(), it will clear the plot keeping the axis. I have not tried it, but it should work

Wrong colors after scaling an image in pygame

I have a 64*64px image of a tree:
I wanted to resize this image for a full screen mode during runtime. I have tried to write some code for this (see below). After executing this program
import pygame, sys
pygame.init()
info = pygame.display.Info()
WINDOWHEIGHT = info.current_h
WINDOWWIDTH = info.current_w
DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH,WINDOWHEIGHT),pygame.FULLSCREEN)
spriteImage = pygame.image.load('Sprite-0003.png')
spriteSurf = pygame.Surface((WINDOWWIDTH,WINDOWHEIGHT))
pygame.transform.scale(spriteImage, (WINDOWWIDTH,WINDOWHEIGHT), spriteSurf)
def close():
pygame.quit()
sys.exit()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
close()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
close()
DISPLAYSURF.blit(spriteSurf, (0,0))
pygame.display.update()
I get this result image. Compare their colors:
At what point did my program decide to change the color of the two images?
And how do I fix it?
First: have a look at pygame.image.load():
For alpha transparency, like in .png images use the convert_alpha()
method after loading so that the image has per pixel transparency.
http://www.pygame.org/docs/ref/image.html#pygame.image.load
Second: Blit function from documentation and stack overflow explanation of each flag:
Documentation:
blit(source, dest, area=None, special_flags = 0) -> Rect
An optional special flags is for passing in new in 1.8.0: BLEND_ADD,
BLEND_SUB, BLEND_MULT, BLEND_MIN, BLEND_MAX new in 1.8.1:
BLEND_RGBA_ADD, BLEND_RGBA_SUB, BLEND_RGBA_MULT, BLEND_RGBA_MIN,
BLEND_RGBA_MAX BLEND_RGB_ADD, BLEND_RGB_SUB, BLEND_RGB_MULT,
BLEND_RGB_MIN, BLEND_RGB_MAX With other special blitting flags perhaps
added in the future.
For a surface with colorkey or blanket alpha, a blit to self may give slightly different colors than a non self-blit.
Stack overflow explanation of each flag:
Basically, ADD adds the two source pixels and clips the result at 255. SUB subtracts the two pixels and clips at 0.
MULT: result = (p1 * p2) / 256
MIN: Select the lower value of each channel (not the whole pixel), so if pixel1 is (100,10,0) and pixel2 is (0,10,100), you get (0,10,0)
MAX: Opposite of MIN (i.e. (100,10,100))
And there is an additional blend mode which isn't obvious from the docs: 0 (or just leave the parameter out). This mode will "stamp" source surface into the destination. If the source surface has an alpha channel, this will be determine how "strong" each pixel is (0=no effect, 255=copy pixel, 128: result = .5*source + .5*destination).
Useful effects: To darken a certain area, use blend mode 0, fill the source/stamp surface black and set alpha to 10: (0,0,0,10).
To lighten it, use white (255,255,255,10).
I think your problem came from alpha channel.
So:
spriteImage = pygame.image.load('Sprite-0003.png').convert_alpha()
Fom:
http://www.pygame.org/docs/ref/surface.html#pygame.Surface.blit
What do the blend modes in pygame mean?
Your source Surface (from the image) and your target Surface don't use the same colorkeys.
Using the default constructor pygame.Surgafe you get a Surface without default colokeys.
Surfaces can have many extra attributes like alpha planes, colorkeys, source rectangle clipping. These functions mainly effect how the Surface is blitted to other Surfaces. The blit routines will attempt to use hardware acceleration when possible, otherwise they will use highly optimized software blitting methods.
Let's consider copying the colokeys, or (best) cloning the image Surface and scale it.

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

Active texturing with pygame (possible? what concepts to look into?)

I have two images:
I'd like to essentially 'cut out' the black shape from the texture tile so that I end up with something along these lines:
Except transparent around the shape. Is this possible using pygame? This example I had to create in GIMP.
Additionally, would it be too performance-heavy to do this for every frame for a few sprites in a real-time environment? (30+ frames per second)
I made a solution, however it is not the best either for speed either for beauty.
You can use double blitting with setting colorkeys for transparency. In that way the mask should have only two colors: black and white.
Note that you can't use this for images with per pixel alpha (RGBA) only for RGB images.
Other restriction is that it is recommended that the size of the texture and the mask image is the same (if not you should use areas for blitting).
In words, step by step:
create a surface with the mask. background should be white (255,255,255), the masked parts should be black (0,0,0)
create or load the texture into a surface
set a transparency colorkey for the mask for the black color
blit the mask onto the texture (or onto a copy of the texture). at this point the black parts of the mask haven't blitted to the texture because we set the black color to transparent with the set_colorkey method
now set the colorkey of the texture (or the copy of the texture) to white. remember that our current texture surface has white and textured parts.
blit the texture to the screen. the white parts won't be blitted due to we have set it to transparent with the colorkey
Code sample:
#!/usr/bin/python
# -*- coding:utf8 -*-
import pygame, sys
#init pygame
pygame.init()
#init screen
screen=pygame.display.set_mode((800,600))
screen.fill((255,0,255))
#loading the images
texture=pygame.image.load("texture.jpg").convert()
texture_rect=texture.get_rect()
texture_rect.center=(200,300)
mask=pygame.Surface((texture_rect.width,texture_rect.height)) # mask should have only 2 colors: black and white
mask.fill((255,255,255))
pygame.draw.circle(mask,(0,0,0),(texture_rect.width/2,texture_rect.height/2),int(texture_rect.width*0.3))
mask_rect=mask.get_rect()
mask_rect.center=(600,300)
tmp_image=texture.copy() # make a copy of the texture to keep it unchanged for future usage
mask.set_colorkey((0,0,0)) # we want the black colored parts of the mask to be transparent
tmp_image.blit(mask,(0,0)) # blit the mask to the texture. the black parts are transparent so we see the pixels of the texture there
tmp_rect=tmp_image.get_rect()
tmp_rect.center=(400,300)
tmp_image.set_colorkey((255,255,255))
screen.blit(texture,texture_rect)
screen.blit(mask,mask_rect)
screen.blit(tmp_image,tmp_rect)
pygame.display.flip()
while 1:
event=pygame.event.wait()
if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key in [pygame.K_ESCAPE, pygame.K_q]):
sys.exit()
I recommend not to use jpg as mask because of its lossy format, I recommend bmp or png (bmp is better). Remember it not uses alpha so the edges won't be anti-aliased so it is not a nice solution in 2010 :)
Here is the screenshot of the result:
Edit:
Hi again,
I made some tests with the blitting with BLEND_ADD and the results was promising. Here is the code:
import pygame, sys
#init pygame
pygame.init()
#init screen
screen=pygame.display.set_mode((800,600))
screen.fill((255,0,255))
#loading the images
texture=pygame.image.load("texture.jpg").convert_alpha()
texture_rect=texture.get_rect()
texture_rect.center=(200,300)
mask=pygame.image.load("mask2.png").convert_alpha()
mask_rect=mask.get_rect()
mask_rect.center=(600,300)
textured_mask=mask.copy()
textured_rect=textured_mask.get_rect()
textured_rect.center=400,300
textured_mask.blit(texture,(0,0),None,pygame.BLEND_ADD)
screen.blit(texture,texture_rect)
screen.blit(mask,mask_rect)
screen.blit(textured_mask,textured_rect)
pygame.display.flip()
while 1:
event=pygame.event.wait()
if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key in [pygame.K_ESCAPE, pygame.K_q]):
sys.exit()
And the result:
With this solution you can get per pixel alpha texturing. Please note that the mask should be an image with alpha channel so jpg could not be used. The best to use is png.
Texture image (texture.jpg):
Mask image (mask2.png):
If you're using images with a pre-rendered alpha and additive blending, you can get alpha-blended masks:
import pygame
import sys
screen = pygame.display.set_mode( (800, 600) )
pygame.init()
background = pygame.image.load( "background.png" )
mask = pygame.image.load( "mask.png" )
# Create a surface to hold the masked image
masked_image = pygame.surface.Surface( background.get_size(), 0, mask )
# Blit the texture normally
masked_image.blit( background, (0,0) )
# Multiply by the pre-rendered, inverted mask
masked_image.blit( mask, (0,0), None, pygame.BLEND_MULT )
# masked_image now holds the 'cutout' of your texture blended onto a black
# background, so we need to blit it as such, i.e., using additive blending.
screen.fill( (0, 0, 0) )
screen.blit( masked_image, (10, 10), None, pygame.BLEND_ADD )
pygame.display.flip()
while True:
event=pygame.event.wait()
if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key in [pygame.K_ESCAPE, pygame.K_q]):
sys.exit()
Where background.png and mask.png are:
+ =

Categories

Resources