pygame - surface from part of another surface - python

how can I create a surface from a section of another surface. Reason is that I have an image as a background, and on that am displaying a clock - so when i update a new time, I need to first restore the original background back on that section before bliting the new clock text. I could do this using flip / full screen update, but want to avoid that and only update / restore a section of the screen.

You can update a section of the screen by passing a a single rectnagle or a list of rectangles to pygame.display.update:
Update portions of the screen for software displays. [...] You can pass the function a single rectangle, or a sequence of rectangles.
e.g.:
pygame.display.update(rect_region)
You can define a subsurface that is directly linked to the source surface with the subsurface method:
Returns a new Surface that shares its pixels with its new parent. The new Surface is considered a child of the original. Modifications to either Surface pixels will effect each other.
e.g.:
rect_region = (x, y, width, height)
subsurf = source_surf.subsurface(rect_region)
Alternatively the blit method allows to specify a rectangular sub-area of a source Surface:
[...] An optional area rectangle can be passed as well. This represents a smaller portion of the source Surface to draw. [...]
e.g.:
rect_region = (x, y, width, height)
traget.blit(source_surf, (posx, posy), rect_region)
See also How can I crop an image with Pygame? and Pygame - blitting from x and y cordinates of image.

Related

What does the parameter DestSurface in Pygame.transform.scale mean and how do I use it?

The documentation for the pygame.transform.scale method is as follows.
scale(Surface, (width, height), DestSurface = None) -> Surface`
Resizes the Surface to a new resolution. This is a fast scale operation that does not sample the results.
An optional destination surface can be used, rather than have it create a new one. This is quicker if you want to repeatedly scale something. However the destination must be the same size as the (width, height) passed in. Also the destination surface must be the same format."
I am trying to figure out what Destination surface is. I am assuming that it has something do with the fact that a new surface object is created whenever I rescale a surface and how I can avoid it using DestSurface ?
pygame.transform.scale() does not scale the input Surface itself. It creates a new surface and does a scaled "blit" to the new surface. The new surface is returned by the return value:
pygame.transform.scale() does:
Creates a new surface (newSurface) with size (width, height).
Scale and copy Surface to newSurface.
Return newSurface.
The destination surface is a pygame.Surface where the scaled surface is copied to. That is quicker, because the memory for the Surface has not to be allocated.
It DestSurface is set, then pygame.transform.scale() does:
Scale and copy Surface to the DestSurface.
Return DestSurface.
For that reason, the size of DestSurface has to be (width, height), the format of DestSurface has the same as the format of Surface.
A possible use case is if you have to continuously scale something to a fixed size in the main application loop (e.g. if the surface is dynamically generated). In the following surf is assumed to be a surface object:
while True:
# [...]
scaledSurf = pygame.transform.scale(surf, (100, 100))
window.blit(scaledSurf, (x, y)
The code can be improved by using the DestSurface parameter:
scaledSurf = pygame.Surface((100, 100))
while True:
# [...]
pygame.transform.scale(surf, (100, 100), scaledSurf)
window.blit(scaledSurf, (x, y)
That is probably a rare case. In general you should try to scale the surfaces at the initialization, rather than continuously in the application loop and to use the scaled surfaces in the loop.
Do not try to use the parameter compulsively and do not "construct" a use case for the DestSurface parameter. Do it the other way around. Write your application and make it run. Then investigate whether the DestSurface parameter can be an improvement for your specific use case.

How to Display Sprites in Pygame?

This is just a quick question regarding sprites in PyGame, I have my image loaded as in the code below, and I'm just wondering how to display the sprite in PyGame like drawing a rectangle or circle. I don't want to have it behave in anyway. I think I use a blit command, but I'm not sure and I'm not finding much online.
Here's my image code for loading it.
Star = pygame.image.load('WhiteStar.png').convert_alpha()
You could just provide an outline for loading a sprite. I simply want to display it.
Use blit to draw an image. Actually blit draws one Surface onto another. Hence you need to blit the image onto the Surface associated to the display.
You need to specify the position where the image is blit on the target. The position can be specified by a pair of coordinates that define the top left position. Or it can be specified by a rectangle, only taking into account the top left point of the rectangle:
screen = pygame.dispaly.set_mode((width, height))
star = pygame.image.load('WhiteStar.png').convert_alpha()
# [...]
while run:
# [...]
screen.blit(star, (x, y))
# [...]
Use a pygame.Rect when you want to place the center of a surface at a specific point. pygame.Surface.get_rect.get_rect() returns a rectangle with the size of the Surface object, that always starts at (0, 0) since a Surface object has no position. The position of the rectangle can be specified by a keyword argument. For example, the center of the rectangle can be specified with the keyword argument center. These keyword argument are applied to the attributes of the pygame.Rect before it is returned (see pygame.Rect for a full list of the keyword arguments):
screen.blit(star, star.get_rect(center = (x, y)))

Pygame Surface Mechanics

I'm currently writing up some GUI code for a small project I'm working on and I've come to the point where I need to implement scroll bars and their associated containers. For ease of execution, I would love to be able to draw all elements within the "scroll box" (the window that the scroll bar will affect) to a separate surface from my main display surface. The separate surface would then be cropped as need be and then drawn to the display surface in the render loop. I'm having trouble getting this to work, however.
In the draw() method of my ScrollBox class, I have the following code.
def draw(self):
self.subSurface.blit(self.image, (x, y))
#subSurface is, naturally, a Surface, and image is a pygame.Image; x and y are whatever
self.displaySurface.blit(self.subSurface, (x,y))
As with all drawable GUI elements in my code, draw() is called every pass through the main render loop. What the above code gives me is the default filled-in black Rect and self.image is not displayed in any capacity. I tried replacing the first line with
pygame.draw.rect(self.subSurface, color, rect)
but it yielded the same results. From my reading up on other Pygame GUI libraries, it seems what I want to do is possible but I don't think I'm executing it properly. How do I attach other sources/surfaces to subSurface and then have subSurface be drawn (with the sources attached) by displaySurface?
Any help would be much appreciated. Thanks.
For people visiting this question in the future:
Remember that the dest argument for Surface.blit() is relative to the upper-left corner of the destination surface. So if you're assembling an image on a subsurface, remember to use coordinates relative to the top-left corner of the object you're assembling, rather than absolute display coordinates.
So to assemble a scrollbar and draw it somewhere:
class ScrollBar:
# ... code ...
def render(self, display, x, y):
self.subSurface.blit(self.handle_image, (0, self.handle_pos))
self.subSurface.blit(self.upbtn_image, (0, 0))
self.subSurface.blit(self.dnbtn_image, (0, self.height - self.btn_height))
# ... other rendering operations
display.blit(self.subSurface, (x, y))
Adjust all numbers and variable names to taste, but you get the idea. Notice that all the scrollbar elements are positioned in "scrollbar-local" coordinates, with only the final blit to the display surface positioned in screen/application coordinates.

What is the correct sequence for bliting surfaces to the screen in pygame?

I am creating a simple mp3 player and my first task was to create a simple button that a user could press. I created a class called Button which handled this behavior and detects if a user has clicked it and then changes color. I am now trying to have a default text that the button displays and another string (pres_string) which will be displayed if the button is being pressed.
The only problem is my background surface seems to be in the wrong place and is drawing over any changes I have made.
Here is my code:
http://pastebin.com/Nh3yy01X
As you can see I've commented out the lines I described and tried it with basic variables in the main function just to test what was going wrong.
Thanks for any help.
(Feel free to change the title of the question, I wasn't sure what most accuratelydescribed my problem)
Clear the surface every loop
def draw(self):
# clear screen."
self.screen.fill( self.color_bg )
# first draw background
# Then buttons
# then any extra top level text
# update
pygame.display.flip()
tip: For colors, you can call pygame.Color() with human-names like red ( gray20 and gray80 have a nice contrast, to use for bg and text. )
from pygame import Color
text = Color('gray20')
Your button, psuedocode. Fix: moved color as an instance member.
class Button(object):
def __init__(self, text, rect=None):
self.color_bg = Color("gray20")
self.color_text = color("gray80")
if rect is None: rect = Rect(0,0,1,1)
self.rect = rect
self._render()
def _render(self):
# draw button and .render() font, cache to surface for later.
self.surface_cached = self.surface.copy()
# render text
#if size changes, save rect.size of cached surface , mantaining location
self.rect.size = cache.get_rect.size
def draw(self):
# draw cached surface
screen.blit( self.surface_cached, self.rect)
For testClick use Rect.collidepoint http://www.pygame.org/docs/ref/rect.html#Rect.collidepoint
2D bitmap-based computer graphics are like drawing or painting - you put the new ink on top of whatever was there already. So your background must be the first thing you draw each time.

Any way to speed up Python and Pygame?

I am writing a simple top down rpg in Pygame, and I have found that it is quite slow.... Although I am not expecting python or pygame to match the FPS of games made with compiled languages like C/C++ or event Byte Compiled ones like Java, But still the current FPS of pygame is like 15. I tried rendering 16-color Bitmaps instead of PNGs or 24 Bitmaps, which slightly boosted the speed, then in desperation , I switched everything to black and white monochrome bitmaps and that made the FPS go to 35. But not more. Now according to most Game Development books I have read, for a user to be completely satisfied with game graphics, the FPS of a 2d game should at least be 40, so is there ANY way of boosting the speed of pygame?
Use Psyco, for python2:
import psyco
psyco.full()
Also, enable doublebuffering. For example:
from pygame.locals import *
flags = FULLSCREEN | DOUBLEBUF
screen = pygame.display.set_mode(resolution, flags, bpp)
You could also turn off alpha if you don't need it:
screen.set_alpha(None)
Instead of flipping the entire screen every time, keep track of the changed areas and only update those. For example, something roughly like this (main loop):
events = pygame.events.get()
for event in events:
# deal with events
pygame.event.pump()
my_sprites.do_stuff_every_loop()
rects = my_sprites.draw()
activerects = rects + oldrects
activerects = filter(bool, activerects)
pygame.display.update(activerects)
oldrects = rects[:]
for rect in rects:
screen.blit(bgimg, rect, rect)
Most (all?) drawing functions return a rect.
You can also set only some allowed events, for more speedy event handling:
pygame.event.set_allowed([QUIT, KEYDOWN, KEYUP])
Also, I would not bother with creating a buffer manually and would not use the HWACCEL flag, as I've experienced problems with it on some setups.
Using this, I've achieved reasonably good FPS and smoothness for a small 2d-platformer.
All of these are great suggestions and work well, but you should also keep in mind two things:
1) Blitting surfaces onto surfaces is faster than drawing directly. So pre-drawing fixed images onto surfaces (outside the main game loop), then blitting the surface to the main screen will be more efficient. For exmample:
# pre-draw image outside of main game loop
image_rect = get_image("filename").get_rect()
image_surface = pygame.Surface((image_rect.width, image_rect.height))
image_surface.blit(get_image("filename"), image_rect)
......
# inside main game loop - blit surface to surface (the main screen)
screen.blit(image_surface, image_rect)
2) Make sure you aren't wasting resources by drawing stuff the user can't see. for example:
if point.x >= 0 and point.x <= SCREEN_WIDTH and point.y >= 0 and point.y <= SCREEN_HEIGHT:
# then draw your item
These are some general concepts that help me keep FPS high.
When using images it is important to convert them with the convert()-function of the image.
I have read that convert() disables alpha which is normally quite slow.
I also had speed problems until I used a colour depth of 16 bit and the convert function for my images. Now my FPS are around 150 even if I blit a big image to the screen.
image = image.convert()#video system has to be initialed
Also rotations and scaling takes a lot of time to calculate. A big, transformed image can be saved in another image if it is immutable.
So the idea is to calculate once and reuse the outcome multiple times.
When loading images, if you absolutely require transparency or other alpha values, use the Surface.convert_alpha() method. I have been using it for a game I've been programming, and it has been a huge increase in performance.
E.G: In your constructor, load your images using:
self.srcimage = pygame.image.load(imagepath).convert_alpha()
As far as I can tell, any transformations you do to the image retains the performance this method calls. E.G:
self.rotatedimage = pygame.transform.rotate(self.srcimage, angle).convert_alpha()
becomes redundant if you are using an image that has had convert_alpha() ran on it.
First, always use 'convert()' because it disables alpha which makes bliting faster.
Then only update the parts of the screen that need to be updated.
global rects
rects = []
rects.append(pygame.draw.line(screen, (0, 0, 0), (20, 20), (100, 400), 1))
pygame.display.update(rects) # pygame will only update those rects
Note:
When moving a sprite you have to include in the list the rect from their last position.
You could try using Psyco (http://psyco.sourceforge.net/introduction.html). It often makes quite a bit of difference.
There are a few things to consider for a well-performing Pygame application:
Ensure that the image Surface has the same format as the display Surface. Use convert() (or convert_alpha()) to create a Surface that has the same pixel format. This improves performance when the image is blit on the display, because the formats are compatible and blit does not need to perform an implicit transformation. e.g.:
surf = pygame.image.load('my_image.png').convert_alpha()
Do not load the images in the application loop. pygame.image.load is a very time-consuming operation because the image file must be loaded from the device and the image format must be decoded. Load the images once before the application loop, but use the images in the application loop.
If you have a static game map that consists of tiles, you can buy performance by paying with memory usage. Create a large Surface with the complete map. blit the area which is currently visible on the screen:
game_map = pygame.Surface((tile_size * columns, tile_size * rows))
for x in range(columns):
for y in range(rows):
tile_image = # image for this row and column
game_map.blit(tile_image , (x * tile_size, y * tile_size))
while game:
# [...]
map_sub_rect = screen.get_rect(topleft = (camera_x, camera_y))
screen.blit(game_map, (0, 0), map_sub_rect)
# [...]
If the text is static, the text does not need to be rendered in each frame. Create the text surface once at the beginning of the program or in the constructor of a class, and blit the text surface in each frame.
If the text is dynamic, it cannot even be pre-rendered. However, the most time-consuming is to create the pygame.font.Font/pygame.font.SysFont object. At the very least, you should avoid creating the font in every frame.
In a typical application you don't need all permutations of fonts and font sizes. You just need a couple of different font objects. Create a number of fonts at the beginning of the application and use them when rendering the text. For Instance. e.g.:
fontComic40 = pygame.font.SysFont("Comic Sans MS", 40)
fontComic180 = pygame.font.SysFont("Comic Sans MS", 180)

Categories

Resources