drawing a pixbuf onto a drawing area using pygtk and glade - python

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.

Related

How can I more efficiently display a pygame screen inside a tkinter GUII Python?

I have made a simulation in pygame. It makes use of a controlled value that the user can set through a tkinter slider and also has a graph displaying some of its statistics (made with matplotlib). Since the pygame, matplotlib and tkinter were all separate windows and I wanted them all in one place as in an app, I first embedded the matplotlib graph into the tk box using FigureCanvasTkAgg. I then tried the same with the pygame window. Having not found an answer on the internet, I then cobbled together a solution:
class PygameWin():
def __init__(self, root):
self.surface = pygame.display.set_mode(
[globalvars.screen_width, globalvars.screen_height])
self.surface.fill((255, 255, 255))
self.canvas = tk.Canvas(
root, height=globalvars.screen_height, width=globalvars.screen_width)
def update(self):
globalvars.amoebas.update()
self.surface.fill((255, 255, 255))
globalvars.amoebas.draw(self.surface)
pygame.image.save(self.surface, 'pygamewin.png')
self.img = ImageTk.PhotoImage(Image.open("pygamewin.png"))
self.canvas.create_image(250,275, image=self.img)
self.canvas.pack()
It basically saves the pygame window as an image and then adds it into a canvas in the tk interface. It works as intended except for the fact that it is very slow and only updates every 100 ticks (times the amoeba are updated). Could anyone help me make it more efficient? I realise that other parts of my code will be impacting the speed, and I can add the full program in if needed.
P.S. Although I can see the pygame window in the tk box as planned, it is still showing up as a separate window as well, so if anyone could tell me how to get rid of that it would be much appreciated.
The reason why your solution is slow is that you are saving the pygame surface as an image and then reloading it every time you want to update the display in tkinter. This is an unnecessary overhead that can slow down the application, especially if you are doing it every 100 ticks.
Instead of saving and reloading the image, you can create a pygame surface that acts as a buffer, draw your amoebas onto that surface, and then convert the surface to a PhotoImage to display it in your tkinter canvas. This approach will reduce the overhead of saving and reloading the image.
Here's an example implementation of this approach:
class PygameWin():
def __init__(self, root):
self.surface = pygame.Surface(
(globalvars.screen_width, globalvars.screen_height))
self.surface.fill((255, 255, 255))
self.canvas = tk.Canvas(
root, height=globalvars.screen_height, width=globalvars.screen_width)
# create a buffer to hold the image data
self.buffer = pygame.Surface(
(globalvars.screen_width, globalvars.screen_height))
def update(self):
globalvars.amoebas.update()
# draw the amoebas onto the buffer
self.buffer.fill((255, 255, 255))
globalvars.amoebas.draw(self.buffer)
# convert the buffer to a PhotoImage and display it in the canvas
img = ImageTk.PhotoImage(Image.frombytes(
'RGB', (self.buffer.get_width(), self.buffer.get_height()),
pygame.image.tostring(self.buffer, 'RGB')))
self.canvas.create_image(0, 0, image=img, anchor='nw')
self.canvas.pack()
# update the pygame display
pygame.display.update()
Regarding the issue of the pygame window showing up as a separate window, you can try setting the position of the window off-screen using the following code:
os.environ['SDL_VIDEO_WINDOW_POS'] = '-1000,-1000'
This should position the window off-screen and prevent it from being visible. You can add this code before the pygame initialization.

How base object controls the camera in Panda3d

I was toying around with creating custom geometry in Panda3d engine. And next code works 100% correct.
class FooBarTriangle(ShowBase):
def __init__(self):
super(self).__init__()
self.disable_mouse()
self.set_frame_rate_meter(True)
self.accept("escape", sys.exit)
self.accept("space", lambda: print(self.camera.get_pos()))
self.camera.set_pos(0, 0, 10)
self.camera.look_at(0, 0, 0)
self._add_light()
self._add_triangle()
def _add_light(self):
# Adds a point light
pass
def _add_triangle(self):
# Adds a single triangle in a certain place
pass
Mysterious things happen when I remove base.disableMouse() from my code. I expect my camera to be movable and appear in the (0, 0, 10) position, looking at (0, 0, 0). But, instead, camera is in the position (0, 0, 0) and I don't know where does it look.
Why does it happen?
This happens, because Panda3D has a default camera control in place (the default camera driver), if you don't call disableMouse(), Panda3D will not move your camera through calls to camera.set_pos(x, y, z), but only allow movement through the specified controls as can be read up here and here in the manual.
You either have to write your own camera controller if you wish to be able to place your camera anywhere other than (0, 0, 0) through code or just use the controls indicated on the links above to move around the scene.

How to make a wxpython Bitmap Button with only image and no extra pixels and border around image

I am trying to use an image as a button in wxpython. What I want is that there should only be a bitmap and no other border or any extra pixel around the button. I would really only want to capture a click on a bitmap, so maybe I do not need an actual button. "Pressing down" the button is hence not required.
One option that I am using a PlateButton with platebutton.PB_STYLE_NOBG which works fine only when it is displaying the image without any mouse hover or clicks. Now when I hover the button, what I want is a shadowed image of same image(only image and no border or anything), but what I get is a square border around my image.
My code :
import wx.lib.platebtn as platebutton
imageButton = platebutton.PlateButton(self._ribbon,wx.ID_NEW, bmp = wx.Bitmap("image.png", wx.BITMAP_TYPE_ANY), pos = (0,0), size = (37,17), style= platebutton.PB_STYLE_DEFAULT | platebutton.PB_STYLE_NOBG)
You can directly use static bitmap and capture the mouse event in this bitmap.
try this:
sBitMap = wx.StaticBitmap(self._ribbon, -1, wx.Bitmap("image.png", wx.BITMAP_TYPE_ANY), (0, 0), (37,17))
sBitMap.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
def OnLeftDown(self, e):
print "OnLeftDown"
Edit:
Fetch the size from the source image and use it to create the static bitmap
sBitMap = wx.StaticBitmap(self._ribbon, -1, wx.Bitmap("image.png", wx.BITMAP_TYPE_ANY), (0, 0), (bmp.GetWidth(), bmp.GetHeight()))
I'm using the GenBitmapButton from wx.lib.buttons
path="path to bitmap"
size=(size of bitmap)
b=GenBitmapButton(Parent,wx.ID_ANY, bitmap=wx.Bitmap(path),
style=wx.NO_BORDER|wx.BU_EXACTFIT,size=size)
This is doing the trick just fine and I have bitmaps lined up with no spacing between them for buttons.

Repainting cairo windows?

I have ran into a problem that i suspect has to do painting/drawing elements in cairo.
I have a borderless window in pygtk, but i draw two rectangles with cairo.a black rectangle, and a grey rectangle inside. When the window is resized, there seems to be parts of the inner rectangle doesn't get drawn/painted. I have included 3 screenshots to show this issue.
As you can see in the second and third picture, some pieces of the window do not get painted grey. One way to fix this, is to call pygtk's window's present() method..but this makes my program extrmely slow, as the height of the window changes with pretty much each keystroke. So i was wondering what alternatives i have to fix this.
below is the relevant cairo code i use
def expose(self, widget, e):
cr = widget.window.cairo_create()
# Draw the background
cr.set_operator(cairo.OPERATOR_SOURCE)
# Create black rectangle with 60% opacity (serves as border)
(width, height) = widget.get_size()
cr.set_source_rgba(0, 0, 0, 0.6)
cr.rectangle(0, 0, width, height)
cr.fill()
# Inside the black rectangle, put a lighter one (will hold widgets)
(width, height) = widget.get_size()
cr.set_source_rgb(205/255, 205/255, 193/255)
cr.rectangle(10, 10, width-20, height-20)
cr.fill()
return False
def screen_changed(self, widget, old_screen = None):
screen = widget.get_screen()
colormap = screen.get_rgba_colormap()
widget.set_colormap(colormap)
It's basically a GTK+ bug, I believe. When a window is resized, GTK+ doesn't always queue the entire window for repainting. As a workaround, you can call window.queue_draw() at the place where you cause the window to be resized.
Try using the following right after you create your cairo widget:
cr.set_source_rgb(0,0,0)
cr.paint()
This will ensure you are always having a clean canvas.

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

Categories

Resources