What is the fastest way to draw an image in Gtk+? - python

I have an image/pixbuf that I want to draw into a gtk.DrawingArea and refresh frequently, so the blitting operation has to be fast. Doing it the easy way:
def __init__(self):
self.drawing_area = gtk.DrawingArea()
self.image = gtk.gdk.pixbuf_new_from_file("image.png")
def area_expose_cb(self, area, event):
self.drawing_area.window.draw_pixbuf(self.gc, self.image, 0, 0, x, y)
However leads to very slow performance, likely caused by the pixbuf not being in the displays color format.
I had no success with Cairo either, as it seems limited to 24/32bit formats and doesn't have a 16bit format (FORMAT_RGB16_565 is unsupported and deprecated).
What alternatives are there to drawing pictures quickly in Gtk+?

Try creating Pixmap that uses the same colormap as your drawing area.
dr_area.realize()
self.gc = dr_area.get_style().fg_gc[gtk.STATE_NORMAL]
img = gtk.gdk.pixbuf_new_from_file("image.png")
self.image = gtk.gdk.Pixmap(dr_area.window, img.get_width(), img.get_height())
self.image.draw_pixbuf(self.gc, img, 0, 0, 0, 0)
and drawing it to the screen using
dr_area.window.draw_drawable(self.gc, self.image, 0, 0, x, y, *self.image.get_size())

Are you really not generating enough raw speed/throughput? Or is it just that you're seeing flickering?
If it's the latter, perhaps you should investigate double buffering for perfomring your updates instead? Basically the idea is to draw to an invisible buffer then tell the graphics card to use the new buffer.
Maybe check out this page which has some information on double buffering.

It may be worth doing some benchmarking - if you draw with a small area is it still slow?
If it is, it may be worth asking on pygtk or gtk mailing lists...

Related

Pyglet: blit_into texture and alpha

I've been using pyglet for a while now and I really like it. I've got one thing I'd like to do but have been unable to do so far, however.
I'm working on a 2D roleplaying game and I'd like the characters to be able to look different - that is to say, I wouldn't like use completely prebuilt sprites, but instead I'd like there to be a range of, say, hairstyles and equipment, visible on characters in the game.
So to get this thing working, I thought the most sensible way to go on about it would be to create a texture with pyglet.image.Texture.create() and blit the correct sprite source images on that texture using Texture.blit_into. For example, I could blit a naked human image on the texture, then blit a hair texture on that, etc.
human_base = pyglet.image.load('x/human_base.png').get_image_data()
hair_style = pyglet.image.load('x/human_hair1.png').get_image_data()
texture = pyglet.image.Texture.create(width=human_base.width,height=human_base.height)
texture.blit_into(human_base, x=0, y=0, z=0)
texture.blit_into(hair_style, x=0, y=0, z=1)
sprite = pyglet.sprite.Sprite(img=texture, x=0, y=0, batch=my_sprite_batch)
The problem is that blitting the second image into the texture "overwrites" the texture already blitted in. Even though both of the images have an alpha channel, the image below (human_base) is not visible after hair_style is blit on top of it.
One reading this may be wondering why do it this way instead of, say, creating two different pyglet.sprite.Sprite objects, one for human_base and one for hair_style and just move them together. One thing is the draw ordering: the game is tile-based and isometric, so sorting a visible object consisting of multiple sprites with differing layers (or ordered groups, as pyglet calls them) would be a major pain.
So my question is, is there a way to retain alpha when using blit_into with pyglet. If there is no way to do it, please, any suggestions for alternative ways to go on about this would be very much appreciated!
setting the blend function correctly should fix this:
pyglet.gl.glBlendFunc(pyglet.gl.GL_SRC_ALPHA,pyglet.gl.GL_ONE_MINUS_SRC_ALPHA)
I ran into the very same problem and couldn't find a proper solution. Apparently blitting two RGBA images/textures overlapping together will remove the image beneath. Another approache I came up with was using every 'clothing image' on every character as an independent sprite attached to batches and groups, but that was far from the optimal and reduced the FPS dramatically.
I got my own solution by using PIL
import pyglet
from PIL import Image
class main(pyglet.window.Window):
def __init__ (self):
TILESIZE = 32
super(main, self).__init__(800, 600, fullscreen = False)
img1 = Image.open('under.png')
img2 = Image.open('over.png')
img1.paste(img2,(0,0),img2.convert('RGBA'))
img = img1.transpose(Image.FLIP_TOP_BOTTOM)
raw_image=img.tostring()
self.image=pyglet.image.ImageData(TILESIZE,TILESIZE,'RGBA',raw_image)
def run(self):
while not self.has_exit:
self.dispatch_events()
self.clear()
self.image.blit(0,0)
self.flip()
x = main()
x.run()
This may well not be the optimal solution, but if you do the loading in scene loading, then it won't matter, and with the result you can do almost almost anything you want to (as long as you don't blit it on another texture, heh). If you want to get just 1 tile (or a column or a row or a rectangular box) out of a tileset with PIL, you can use the crop function.

setting alpha using pixels_alpha in pygame

I'm using pygame for image editing (not displaying). (Yes, I know there are other options like PIL/pillow, but pygame should work for this.)
I want to draw and save an image where I'm individually setting the alpha values of each pixel according to a formula (I'm drawing a complicated RGBA profile). It seems that pixels_alpha is the right way to do this. But when I change pixels_alpha it's ignored, the image just stays transparent. Here's my code...
import pygame as pg
import os
def init_transparent(img_width, img_height):
"""
Create a new surface with width and height, starting with transparent
background. This part works!
"""
os.environ['SDL_VIDEODRIVER'] = 'dummy'
pg.init()
pg.display.init()
# next command enables alpha
# see http://stackoverflow.com/questions/14948711/
pg.display.set_mode((img_width, img_height), 0, 32)
# pg.SRCALPHA is a flag that turns on per-pixel alpha.
return pg.Surface((img_width, img_height), pg.SRCALPHA)
def make_semitransparent_image():
surf = init_transparent(20, 20)
# alphas should be a direct reference to the alpha data in surf
alphas = pg.surfarray.pixels_alpha(surf)
# alphas is a uint8-dtype numpy array. Right now it's all zeros.
for i in range(20):
for j in range(20):
# Since pixels_alpha gave me a uint8 array, I infer that
# alpha=255 means opaque. (Right?)
alphas[i,j] = 200
pg.image.save(surf, '/home/steve/Desktop/test.png')
make_semitransparent_image()
Again, it saves an image but the image looks completely transparent, no matter what value I set alphas to. What am I doing wrong here? (Using Python 2.7, pygame 1.9.1release.) (Thanks in advance!)
The Pygame docs say that pixels_array() locks the surface as long as the resulting array exists, and that locked surface may not be drawn correctly. It seems to be the case that they are not saved correctly, too. You need to throw away the alphas surfarray object before calling image.save(). This can be done by adding del alphas just before image.save().
(Note that this is more a workaround than a proper fix. Adding del alphas can only have a reliable effect only on CPython (and not PyPy, Jython, IronPython). Maybe you can really "close" a surfarray objects, the same way you can either close a file or merely forget about it?)

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)

Eliminate unnecessary drawing of an image when using dc?

I have three questions that I could really use some help on. Hope I'm not asking too much.
1) I am designing a simple GUI that contains one frame and one panel. Let's say I have two images that I draw on the panel using dc. One image will be continually fade in and out (on a timer), and the second is stationary (doesn't change). The fading is accomplished by changing the opacity of the image and use dc.Clear() before redrawing the new version of the image.
My question is this: how would I draw the fading in/out image without affecting the second image which does not change? It seems like this causes unnecessary drawing as the stationary image will be redrawn alongside the fading image. Could I selectively clear just the first image without affecting the second? This is my drawing function:
def on_paint(self, event):
dc = wx.PaintDC(self)
dc = wx.BufferedDC(dc)
brush = wx.Brush('#3B3B3B')
dc.SetBackground(brush)
dc.Clear()
# Draw the first image (stationary)
dc.DrawBitmap(stationaryBitmap, 120, 0, True)
# Draw the second image (fading)
image = self.image.AdjustChannels(1, 1, 1, self.factoralpha)
fadingBitmap = wx.BitmapFromImage(image)
dc.DrawBitmap(fadingBitmap, 120, 0, True)
2) How can I bind an event to a wx.Image object? I would like to be able to click on the fading in/out image, though I can't seem to assign it an id. The goal is to bind an event similar to what I could do with a wx.StaticBitmap.
self.image = wx.Image("C:\image.png", wx.BITMAP_TYPE_PNG)
# Trying to bind an event, but no ID is assigned
self.Bind(wx.EVT_BUTTON, self.go_button, id=self.image.GetId())
3) Is it possible to place wx.DrawBitmap in a sizer? It appears that it only takes an x,y coordinate pair.
dc.DrawBitmap(bitmap, 120, 0, True)
Thanks everyone.
1) For the Performance, I would recommend using a MemoryDC and update the Drawing only it is required. See here: BufferedCanvas. You may want to use more than 2 buffers because you are using 2 images (see example).
2) I don't know about this, but have you tried to do the binding to a panel and fade the panel in/out?
You can directly paint on a wx.Panel.
Regards

drawing a pixbuf onto a drawing area using pygtk and glade

i'm trying to make a GTK application in python where I can just draw a loaded image onto the screen where I click on it. The way I am trying to do this is by loading the image into a pixbuf file, and then drawing that pixbuf onto a drawing area.
the main line of code is here:
def drawing_refresh(self, widget, event):
#clear the screen
widget.window.draw_rectangle(widget.get_style().white_gc, True, 0, 0, 400, 400)
for n in self.nodes:
widget.window.draw_pixbuf(widget.get_style().fg_gc[gtk.STATE_NORMAL],
self.node_image, 0, 0, 0, 0)
This should just draw the pixbuf onto the image in the top left corner, but nothing shows but the white image. I have tested that the pixbuf loads by putting it into a gtk image. What am I doing wrong here?
You can make use of cairo to do this. First, create a gtk.DrawingArea based class, and connect the expose-event to your expose func.
class draw(gtk.gdk.DrawingArea):
def __init__(self):
self.connect('expose-event', self._do_expose)
self.pixbuf = self.gen_pixbuf_from_file(PATH_TO_THE_FILE)
def _do_expose(self, widget, event):
cr = self.window.cairo_create()
cr.set_operator(cairo.OPERATOR_SOURCE)
cr.set_source_rgb(1,1,1)
cr.paint()
cr.set_source_pixbuf(self.pixbuf, 0, 0)
cr.paint()
This will draw the image every time the expose-event is emited.
I found out I just need to get the function to call another expose event with widget.queue_draw() at the end of the function. The function was only being called once at the start, and there were no nodes available at this point so nothing was being drawn.

Categories

Resources