This code successfully draws the SVG onto the screen. However, when I resize the window, the original image stays superimposed over the screen, while it redraws it underneath.
Before Resizing:
After Maximizing:
It's especially noticable if you drag to resize
# import igraph # either uncomment and install igraph or provide output.svg in the same dir
import wx
import wx.svg
class NNGui(wx.Frame):
def __init__(self, parent, title):
super().__init__(parent, title=title, size=(800, 600))
self.panel = wx.Panel(self)
vbox = wx.BoxSizer(wx.VERTICAL)
# main graphics box
self.screen = MainScreen(self.panel)
vbox.Add(self.screen, proportion=1, flag=wx.EXPAND | wx.ALL, border=10)
self.Bind(wx.EVT_SIZE, self.on_resize)
self.Bind(wx.EVT_MAXIMIZE, self.on_resize)
# command box
self.cmd_box = wx.TextCtrl(self.panel, style=wx.TE_PROCESS_ENTER | wx.TE_PROCESS_TAB)
vbox.Add(self.cmd_box, flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border=10)
self.cmd_box.Bind(wx.EVT_CHAR, self.do_char)
self.panel.SetSizer(vbox)
self.Layout()
self.Centre()
def do_char(self, e):
# handle keypresses
e.Skip()
def on_resize(self, e):
print('resize!')
# self.screen = MainScreen(self.panel)
self.screen.Refresh() # thank you Rolf-of-Saxony
# self.panel.Refresh()
# self.Refresh()
self.screen.Update()
# self.panel.Update()
# self.Update()
e.Skip()
class MainScreen(wx.Panel):
def __init__(self, parent):
super().__init__(parent)
self.img = wx.svg.SVGimage.CreateFromFile('output.svg')
self.Bind(wx.EVT_PAINT, self.on_paint)
def on_paint(self, e):
print('screen painted!')
dc = wx.PaintDC(self)
dc.SetBackground(wx.Brush('black'))
dc.Clear()
dc_dim = min(self.Size.width, self.Size.height)
img_dim = min(self.img.width, self.img.height)
scale = dc_dim / img_dim
width = int(self.img.width * scale)
height = int(self.img.height * scale)
# ctx = wx.GraphicsContext.Create(dc)
# self.img.RenderToGC(ctx, scale)
bmp = self.img.ConvertToBitmap(scale=scale, width=width, height=height)
px_to_center = int((self.Size.width - width) / 2)
dc.DrawBitmap(bmp, px_to_center, 0)
e.Skip()
class NNGraph:
def __init__(self):
self.g = igraph.Graph.GRG(50, 0.2)
def write_svg(self, filename='output.svg'):
assert filename.endswith('.svg')
igraph.plot(self.g, filename)
def main():
# graph = NNGraph() # either uncomment and install igraph or provide output.svg in the same dir
# graph.write_svg()
app = wx.App()
frame = NNGui(None, title='NeuronicNodes')
frame.Show()
app.MainLoop()
if __name__ == '__main__':
main()
I've tried applying Update() to several panels, bound events for EVT_SIZE, EVT_PAINT, tried recreating the panel... I'm not sure what I'm missing.
I want to put such an image in a wx.Panel :
The animals should be "buttons" so that if I click on them, their image changes, and if I reclick, the image returns to normal (thus the animals can be considered as simple BitmapToggleButtons, as suggested by another question here on SO)
This panel should be resized/rescaled (all all the children images / togglebuttons too!) keeping the aspect ratio, if the parent wx.Panel is resized to something smaller for example (like would do the standard Windows Photo Viewer : http://res1.windows.microsoft.com/resbox/en/windows%207/main/7eaf462a-86dd-42d2-a789-7413f5472dae_63.jpg)
I am still a bit lost on : how to implement such a clickable (with toggle buttons) and rescalable Canvas?
Edit : I started with something fruitful here Rescale image when parent is resized in wxPython, but now I'm totally stuck about how to continue (detect clicks, update buttons with direct DC Painting ?), that's why the bounty.
You will have to implement your own hit testing, i.e. be able to determine where each animal is -- this is the difficult part and there is really nothing in wxWidgets to help you with this. The rest is relatively simple, you might even be able to use the existing wxMouseEventsManager to avoid writing the boilerplate code yourself (but if you can't, you can at least look at its implementation, which is done entirely in wxWidgets itself, to see what you need to do).
Depending on how much of this is already written, you may want to take a look at FloatCanvas (it's in the wxPython library).
If you have most of the code-base done, you can use a hit test, which is rather simple. Just make a dictionary with the [x][y] coordinates as keys which has the the BitmapTogglebutton as its value.
Here's some code that does something similar (it's been a while since I've used wxPython so it may not be 100%):
def onLeftDown( event ):
x,y = event.GetX(), event.GetY()
hitmap_x = hitmap.get(x,None)
if hitmap_x:
btn = hitmap_x.get(y, None)
...stuff with btn like toggles
I made some code for practice recently. It may somehow match with your requirement.
Code is ugly and mess because I'm a newcomer to python.
Support:
image on the backgroud draggable
image animating with double-click
background and image on it resizeable
Notice:
you need have pygame to run the code
you can load real image by replacing PyGamePseudoImage()
image coordinate adjustment is not smooth enough while zoom in/out
Code:
import wx
import pygame
BLACK = ( 0, 0, 0)
WHITE = (255, 255, 255)
BLUE = ( 0, 0, 255)
GREEN = ( 0, 255, 0)
RED = (255, 0, 0)
pygame.font.init()
try:
regular_font_file = os.path.join(os.path.dirname(__file__), "Vera.ttf")
bold_font_file = os.path.join(os.path.dirname(__file__), "VeraBd.ttf")
# Check for cx_Freeze
#
if "frozen" in sys.__dict__.keys() and sys.frozen:
regular_font_file = os.path.join(sys.path[1], "Vera.ttf")
bold_font_file = os.path.join(sys.path[1], "VeraBd.ttf")
BIG_FONT = pygame.font.Font(regular_font_file, 30)
SMALL_FONT = pygame.font.Font(regular_font_file, 12)
BOLD_FONT = pygame.font.Font(bold_font_file, 12)
except:
# TODO: log used font: pygame.font.get_default_font()
#print("Could not load {0}".format(os.path.join(os.path.dirname(__file__), "Vera.ttf")))
BIG_FONT = pygame.font.Font(None, 40)
SMALL_FONT = BOLD_FONT = pygame.font.Font(None, 20)
class PyGamePseudoImage():
def __init__(self, size, color):
self.screen = pygame.Surface(size, 0, 32)
self.screen.fill(color)
def getImage(self):
return self.screen
class __MouseMixin:
def onLeftUp(self, event):
pass
def onLeftDown(self, event):
pass
def onLeftDClick(self, event):
pass
def onRightUp(self, event):
pass
def onRightDown(self, event):
pass
def onDragging(self, event):
pass
def onMouseEnter(self, event):
pass
def OnMouseHandler(self, event):
event.Skip()
if event.LeftUp():
self.onLeftUp(event)
elif event.LeftDown():
self.onLeftDown(event)
elif event.LeftDClick():
self.onLeftDClick(event)
elif event.RightUp():
self.onRightUp(event)
elif event.RightDown():
self.onRightDown(event)
elif event.Dragging() and event.LeftIsDown():
self.onDragging(event)
pass
class DragSprite(__MouseMixin, pygame.sprite.Sprite):
SPRITE_BUTTON, SPRITE_TRANSPORTER = range(2)
def __init__(self, parent=None):
pygame.sprite.Sprite.__init__(self)
self.is_select = 0
self.lastPos = 0
self.lastUpdate = 0
self.parent = parent
def setLastPos(self, pos):
self.lastPos = pos
def move(self, pos):
dx = pos[0] - self.lastPos[0]
dy = pos[1] - self.lastPos[1]
self.lastPos = pos
center = (self.rect.center[0] + dx, self.rect.center[1] + dy)
self.rect.center = center
return
def isSelected(self):
return self.is_select
def setSelect(self, is_select):
self.is_select = is_select
return
def update(self, current_time):
return
def drawBoader(image, rect):
W,H = (rect.width, rect.height)
yellow = (255, 255, 0)
pygame.draw.rect(image, yellow, (0,0,W-2,H-2), 2)
class ButtonSprite(DragSprite):
def __init__(self, parent=None, initPos=(0,0), width=50, height=50, dicts=None):
DragSprite.__init__(self, parent)
self.type = DragSprite.SPRITE_BUTTON
self.resourceCfgDict = dicts
self.imageResource = {}
self.status = 0
self.index = 0
self.parent = parent
self.initPos = (initPos[0], initPos[1])
self.width = width
self.height = height
self.rectOnLoad = pygame.Rect(initPos, (width, height))
self.rect = self.rectOnLoad.copy()
self.operationOn = None
self.operationOff = None
self.operationDic = {"on": self.getOperationOnItem, "off": self.getOperationOffItem}
self.guiCfg = None
for dic in dicts:
self.loadImgResource(dic)
self.setCurrentResource("off")
def getOperationOnItem(self):
return self.operationOn
def getOperationOffItem(self):
return self.operationOff
def loadImgResource(self, dict):
"""
load image with pygame lib
"""
key = dict[0]
file_name = dict[1]
#image_file = pygame.image.load(file_name) #use this to load real image
image_file = PyGamePseudoImage((500,500), file_name).getImage()
imagedata = pygame.image.tostring(image_file, "RGBA")
imagesize = image_file.get_size()
imageSurface = pygame.image.fromstring(imagedata, imagesize , "RGBA")
self.imageResource[key] = (file_name, imageSurface)
def resizeResource(self, src, size):
return pygame.transform.smoothscale(src, size)
def setCurrentResource(self, status):
self.currentStatus = status
self.imageOnLoad = self.resizeResource(self.imageResource[status][1], (self.width, self.height))
self.image = pygame.transform.scale(self.imageOnLoad, (self.rect.width, self.rect.height))
def switchResource(self, index):
self.setCurrentResource(index)
def onZoomUpdate(self, zoomRatio):
parentRect = pygame.Rect(self.parent.GetRect())
dx = self.rectOnLoad.centerx - parentRect.centerx
dy = self.rectOnLoad.centery - parentRect.centery
self.rect.centerx = parentRect.centerx + dx*zoomRatio
self.rect.centery = parentRect.centery + dy*zoomRatio
self.rect.height = self.imageOnLoad.get_rect().height * zoomRatio
self.rect.width = self.imageOnLoad.get_rect().width * zoomRatio
self.image = pygame.transform.scale(self.imageOnLoad, (self.rect.width, self.rect.height))
def update(self, current_time, ratio):
if self.isSelected():
drawBoader(self.image, self.image.get_rect())
else:
pass
#self.image = self.imageOnLoad.copy()
def onRightUp(self, event):
print "onRightUp"
event.Skip(False)
pass
def onLeftDClick(self, event):
if self.currentStatus == "on":
self.setCurrentResource("off")
elif self.currentStatus == "off":
self.setCurrentResource("on")
return
def move(self, pos):
DragSprite.move(self, pos)
parentRect = pygame.Rect(self.parent.GetRect())
centerDx = self.rect.centerx - parentRect.centerx
centerDy = self.rect.centery - parentRect.centery
self.rectOnLoad.centerx = parentRect.centerx + centerDx/self.parent.zoomRatio
self.rectOnLoad.centery = parentRect.centery + centerDy/self.parent.zoomRatio
class MyHmiPanel(wx.Panel):
def __init__(self, parent, ID):
wx.Window.__init__(self, parent, ID)
self.parent = parent
self.hwnd = self.GetHandle()
self.size = self.GetSizeTuple()
self.size_dirty = True
self.rootSpriteGroup = pygame.sprite.LayeredUpdates()
self.timer = wx.Timer(self)
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_TIMER, self.Update, self.timer)
self.Bind(wx.EVT_SIZE, self.OnSize)
self.fps = 60.0
self.timespacing = 1000.0 / self.fps
self.timer.Start(self.timespacing, False)
self.previous_time = 0
self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouse)
self.selectedSprite = None
self.zoomRatio = 1
self.background = None
self.bgRect = None
self.backgroundOnUpdate = None
self.bgRetOnUpdate = None
self.loadBackground()
self.addTestSprite()
def loadBackground(self):
#self.background = pygame.image.load(image_file) #use this to load real image
self.background = PyGamePseudoImage((500,500), (255, 0, 0)).getImage()
self.bgRect = self.background.get_rect()
self.backgroundOnUpdate = self.background.copy()
self.bgRetOnUpdate = self.bgRect.copy()
def resizeUpdateBackground(self):
self.bgRect.center = self.screen.get_rect().center
self.bgRetOnUpdate = self.bgRect.copy()
def zoomUpdateBackground(self, zoomRatio):
self.bgRetOnUpdate.width = self.bgRect.width * zoomRatio
self.bgRetOnUpdate.height = self.bgRect.height * zoomRatio
self.bgRetOnUpdate.width = self.bgRect.width * zoomRatio
self.bgRetOnUpdate.center = self.screen.get_rect().center
self.backgroundOnUpdate = pygame.transform.scale(self.background, (self.bgRetOnUpdate.width, self.bgRetOnUpdate.height))
def drawBackground(self, screen):
screen.blit(self.backgroundOnUpdate, self.bgRetOnUpdate)
def addTestSprite(self):
#self.rootSpriteGroup.add(ButtonSprite(self, initPos=(100, 100), width=100, height=100, dicts= [('on', btn_red_on), ('off', btn_red_off)]))
#self.rootSpriteGroup.add(ButtonSprite(self, initPos=(200, 200), width=100, height=100, dicts= [('on', btn_red_on), ('off', btn_red_off)]))
self.rootSpriteGroup.add(ButtonSprite(self, initPos=(100, 100), width=100, height=100, dicts= [('on', GREEN), ('off', BLUE)]))
self.rootSpriteGroup.add(ButtonSprite(self, initPos=(200, 200), width=100, height=100, dicts= [('on', GREEN), ('off', BLUE)]))
def Update(self, event):
self.Redraw()
return
def Redraw(self):
if self.size[0] == 0 or self.size[1] == 0:
return
if self.size_dirty:
self.screen = pygame.Surface(self.size, 0, 32)
self.resizeUpdateBackground()
self.size_dirty = False
self.screen.fill((0,0,0))
self.drawBackground(self.screen)
w, h = self.screen.get_size()
current_time = pygame.time.get_ticks()
self.previous_time = current_time
self.rootSpriteGroup.update(current_time, self.zoomRatio)
self.rootSpriteGroup.draw(self.screen)
s = pygame.image.tostring(self.screen, 'RGB') # Convert the surface to an RGB string
#img = wx.ImageFromData(self.size[0], self.size[1], s) # Load this string into a wx image
img = wx.ImageFromData(w, h, s) # Load this string into a wx image
#if img.IsOk() is not True:
# return
bmp = wx.BitmapFromImage(img) # Get the image in bitmap form
dc = wx.ClientDC(self) # Device context for drawing the bitmap
dc = wx.BufferedDC( dc)
dc.DrawBitmap(bmp, 0, 0, 1) # Blit the bitmap image to the display
def checkCollide(self, event):
x , y = (event.GetX(),event.GetY())
mousePoint = pygame.sprite.Sprite()
mousePoint.rect = pygame.Rect(x, y, 1, 1)
copoint = pygame.sprite.spritecollide(mousePoint, self.rootSpriteGroup, None)
if copoint:
copoint = copoint[-1]
return copoint
def removeSelectedSprite(self):
if self.selectedSprite:
self.selectedSprite.setSelect(0)
self.selectedSprite = None
def setNewSelectedSprite(self, sprite):
self.removeSelectedSprite()
sprite.setSelect(1)
self.selectedSprite = sprite
def onSelectSprite(self, event, onMouseObj):
if onMouseObj:
if self.selectedSprite:
if onMouseObj != self.selectedSprite:
self.setNewSelectedSprite(onMouseObj)
else:
self.setNewSelectedSprite(onMouseObj)
self.selectedSprite.setLastPos((event.GetX(),event.GetY()))
else:
self.removeSelectedSprite()
def OnMouse(self, event):
onMouseObj = self.checkCollide(event)
event.Skip()
if onMouseObj:
onMouseObj.OnMouseHandler(event)
if not event.GetSkipped():
print "event dropped "
return
if event.LeftDown():
self.onSelectSprite(event, onMouseObj)
elif event.LeftUp():
pass
elif event.RightUp():
self.onSelectSprite(event, onMouseObj)
elif event.RightDown():
self.onSelectSprite(event, onMouseObj)
elif event.Dragging() and event.LeftIsDown():
if self.selectedSprite:
self.selectedSprite.move((event.GetX(),event.GetY()))
def OnPaint(self, event):
self.Redraw()
event.Skip() # Make sure the parent frame gets told to redraw as well
def OnSize(self, event):
self.size = self.GetSizeTuple()
self.size_dirty = True
def Kill(self, event):
self.Unbind(event=wx.EVT_PAINT, handler=self.OnPaint)
self.Unbind(event=wx.EVT_TIMER, handler=self.Update, source=self.timer)
def onZoomIn(self):
self.zoomRatio += 0.2
self.onZoomUpdate()
def onZoomReset(self):
self.zoomRatio = 1
self.onZoomUpdate()
def onZoomOut(self):
if self.zoomRatio > 0.2:
self.zoomRatio -= 0.2
self.onZoomUpdate()
def onZoomUpdate(self):
self.zoomUpdateBackground(self.zoomRatio)
for s in self.rootSpriteGroup.sprites():
s.onZoomUpdate(self.zoomRatio)
class TestFrame ( wx.Frame ):
def __init__( self, parent, fSize ):
wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = wx.EmptyString, pos = wx.DefaultPosition, size = fSize, style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL )
self.SetSizeHintsSz( wx.DefaultSize, wx.DefaultSize )
fgSizer1 = wx.FlexGridSizer( 2, 1, 0, 0 )
fgSizer1.AddGrowableCol( 0 )
fgSizer1.AddGrowableRow( 0 )
fgSizer1.SetFlexibleDirection( wx.VERTICAL )
fgSizer1.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_ALL )
self.panelMain = MyHmiPanel(self, -1)
fgSizer1.Add( self.panelMain, 1, wx.EXPAND |wx.ALL, 5 )
self.m_panel4 = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL )
bSizer3 = wx.BoxSizer( wx.HORIZONTAL )
self.bZoomIn = wx.Button( self.m_panel4, wx.ID_ANY, u"Zoom In", wx.DefaultPosition, wx.DefaultSize, 0 )
bSizer3.Add( self.bZoomIn, 0, wx.ALL, 5 )
self.bReset = wx.Button( self.m_panel4, wx.ID_ANY, u"Reset", wx.DefaultPosition, wx.DefaultSize, 0 )
bSizer3.Add( self.bReset, 0, wx.ALL, 5 )
self.bZoomOut = wx.Button( self.m_panel4, wx.ID_ANY, u"Zoom Out", wx.DefaultPosition, wx.DefaultSize, 0 )
bSizer3.Add( self.bZoomOut, 0, wx.ALL, 5 )
self.m_panel4.SetSizer( bSizer3 )
self.m_panel4.Layout()
bSizer3.Fit( self.m_panel4 )
fgSizer1.Add( self.m_panel4, 1, wx.EXPAND |wx.ALL, 5 )
self.SetSizer( fgSizer1 )
self.Layout()
self.Centre( wx.BOTH )
self.bZoomIn.Bind( wx.EVT_BUTTON, self.onZoomIn )
self.bReset.Bind( wx.EVT_BUTTON, self.onZoomReset )
self.bZoomOut.Bind( wx.EVT_BUTTON, self.onZoomOut )
def __del__( self ):
pass
def onZoomIn( self, event ):
self.panelMain.onZoomIn()
event.Skip()
def onZoomReset( self, event ):
self.panelMain.onZoomReset()
event.Skip()
def onZoomOut( self, event ):
self.panelMain.onZoomOut()
event.Skip()
if __name__=='__main__':
app = wx.App(redirect=False)
frame = TestFrame(None, (800, 600))
frame.SetPosition((100, 100))
frame.Show()
app.MainLoop()
I solved the problem with :
import wx
from floatcanvas import FloatCanvas
class MyPanel(wx.Panel):
def __init__(self, parent):
super(MyPanel, self).__init__(parent)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(self.sizer)
# add a canvas
self.Canvas = FloatCanvas.FloatCanvas(self, BackgroundColor = "LIGHT GREY")
self.Canvas.Bind(wx.EVT_SIZE, self.OnSize)
self.sizer.Add(self.Canvas, -1, flag=wx.EXPAND)
# add a toggle button
image_dis = wx.Image('file_disabled.png')
image_ena = wx.Image('file_enabled.png')
img_dis = self.Canvas.AddScaledBitmap(image_dis, (x,-y), Height=image_dis.GetHeight(), Position = 'tl')
img_ena = self.Canvas.AddScaledBitmap(image_ena, (x,-y), Height=image_ena.GetHeight(), Position = 'tl')
img_dis.other = img_ena
img_ena.other = img_dis
img_ena.Visible = False
# bind the toggle button event
img_dis.Bind(FloatCanvas.EVT_FC_LEFT_UP, self.OnToggle)
img_ena.Bind(FloatCanvas.EVT_FC_LEFT_UP, self.OnToggle)
def OnToggle(self, button):
button.other.Visible = True
button.Visible = False
self.Canvas.Draw(True)
def OnSize(self, event):
event.Skip()
wx.CallLater(1, self.Canvas.ZoomToBB)
I can't answer the scaling issue, but an old trick I recall for doing the arbitrary image target hit-checking (no buttons required) goes like this:
1) Create a blank invisible image the same size as the visible one.
2) As you draw targets on the main image, draw an identically shaped "shadow" to the invisible with all the same value pixel (but a unique value for every target). A "handle", if you will.
3) When you get a mouse click on the main image, use the coordinates to get the same pixel from your invisible shadow image. The value will be the handle for the target.
Simple once you hear it, isn't it?
How do I show a image from a PNG, BITMAP, JPEG file, ect. on the screen without a window? I want the image to appear with no frame surrounding it preferably without it registering on the task bar as a window. I would like to put these images on the screen in rapid succession. I would like it if it was compatible with Windows XP and Windows 7. I am willing to download a external module. If this is possible, please tell me. Thanks!
This will create a shaped window with transparent background displaying the "image.png" in same folder
import wx
# Create a .png image with something drawn on a white background
# and put the path to it here.
IMAGE_PATH = 'image.png'
class ShapedFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, "Shaped Window",
style = wx.FRAME_SHAPED | wx.SIMPLE_BORDER)
self.hasShape = False
self.delta = wx.Point(0,0)
# Load the image
image = wx.Image(IMAGE_PATH, wx.BITMAP_TYPE_PNG)
image.SetMaskColour(255,255,255)
image.SetMask(True)
self.bmp = wx.BitmapFromImage(image)
self.SetClientSize((self.bmp.GetWidth(), self.bmp.GetHeight()))
dc = wx.ClientDC(self)
dc.DrawBitmap(self.bmp, 0,0, True)
self.SetWindowShape()
self.Bind(wx.EVT_LEFT_DCLICK, self.OnDoubleClick)
self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
self.Bind(wx.EVT_MOTION, self.OnMouseMove)
self.Bind(wx.EVT_RIGHT_UP, self.OnExit)
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_WINDOW_CREATE, self.SetWindowShape)
self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
def OnEraseBackground(self,evt=None):
pass
def SetWindowShape(self, evt=None):
r = wx.RegionFromBitmap(self.bmp)
self.hasShape = self.SetShape(r)
def OnDoubleClick(self, evt):
if self.hasShape:
self.SetShape(wx.Region())
self.hasShape = False
else:
self.SetWindowShape()
def OnPaint(self, evt):
dc = wx.PaintDC(self)
dc.DrawBitmap(self.bmp, 0,0, True)
def OnExit(self, evt):
self.Close()
def OnLeftDown(self, evt):
self.CaptureMouse()
pos = self.ClientToScreen(evt.GetPosition())
origin = self.GetPosition()
self.delta = wx.Point(pos.x - origin.x, pos.y - origin.y)
def OnMouseMove(self, evt):
if evt.Dragging() and evt.LeftIsDown():
pos = self.ClientToScreen(evt.GetPosition())
newPos = (pos.x - self.delta.x, pos.y - self.delta.y)
self.Move(newPos)
def OnLeftUp(self, evt):
if self.HasCapture():
self.ReleaseMouse()
if __name__ == '__main__':
app = wx.PySimpleApp()
ShapedFrame().Show()
app.MainLoop()
This is the image I used
I am creating a small drawing application from a python book, "wxPython in Action", and it uses self.GetClientSize() to get the size of a window. For some reason this is return (0, 0) for me instead of the expected value (800, 600).
The program crashes when wx.EmptyBitmap is called with 0, 0 as its parameters. If I put
wx.EmptyBitmap(800, 600) the entire program runs fine, minus resizing.
Here is the relevant method
def InitBuffer(self):
size = self.GetClientSizeTuple()
print size
sys.exit(1)
self.buffer = wx.EmptyBitmap(size.width, size.height)
dc = wx.BufferedDC(None, self.buffer)
dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
dc.Clear()
self.DrawLines(dc)
self.reInitBuffer = False
And this is the complete code
#!/usr/bin/arch -i386 /usr/bin/python2.6 -tt
import sys
import wx
class SketchWindow(wx.Window):
def __init__(self, parent, ID):
wx.Window.__init__(self, parent, ID)
self.SetBackgroundColour("White")
self.color = "Black"
self.thickness = 1
self.pen = wx.Pen(self.color, self.thickness, wx.SOLID)
self.lines = []
self.curLine = []
self.pos = (0, 0)
self.InitBuffer()
self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
self.Bind(wx.EVT_SIZE, self.OnSize)
self.Bind(wx.EVT_MOTION, self.OnMotion)
self.Bind(wx.EVT_IDLE, self.OnIdle)
self.Bind(wx.EVT_PAINT, self.OnPaint)
def InitBuffer(self):
size = self.GetClientSizeTuple()
print size
sys.exit(1)
self.buffer = wx.EmptyBitmap(size.width, size.height)
dc = wx.BufferedDC(None, self.buffer)
dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
dc.Clear()
self.DrawLines(dc)
self.reInitBuffer = False
def GetLinesData(self):
return self.lines[:]
def SetLinesData(self, lines):
self.lines = lines[:]
self.InitBuffer()
self.Refresh()
def OnLeftDown(self, event):
self.curLine = []
self.pos = event.GetPositionTuple()
self.CaptureMouse()
def OnLeftUp(self, event):
if self.HasCapture():
self.lines.append((self.color, self.thickness, self.curLine))
self.curLine = []
self.ReleaseMouse()
def OnMotion(self, event):
if event.Dragging() and event.LeftIsDown():
dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)
self.drawMotion(dc, event)
event.Skip()
def drawMotion(self, dc, event):
dc.SetPen(self.pen)
newPos = event.GetPositionTuple()
coords = self.pos + newPos
self.curLine.append(coords)
dc.DrawLine(*coords)
self.pos = newPos
def OnSize(self, event):
self.reInitBuffer = True
def OnIdle(self, event):
if self.reInitBuffer:
self.InitBuffer()
self.Refresh(False)
def OnPaint(self, event):
dc = wx.BufferedPaintDC(self, self.buffer)
def DrawLines(self, dc):
for (colour, thickness, line) in self.lines:
pen = wx.Pen(colour, thickness, wx.SOLID)
dc.SetPen(pen)
for coord in line:
dc.DrawLine(*coord)
def SetColor(self, color):
self.color = color
self.pen = wx.Pen(self.color, self.thickness, wx.SOLID)
def GetColor(self):
return self.color
def SetThickness(self, thickness):
self.thickness = thickness
self.pen = wx.Pen(self.color, self.thickness, wx.SOLID)
class SketchFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, -1, 'Sketch Frame', size=(800, 600))
self.sketch = SketchWindow(self, -1)
def main():
app = wx.PySimpleApp()
frame = SketchFrame(None)
frame.Show(True)
app.MainLoop()
if __name__ == '__main__':
main()
It's because you're calling GetSize in the __init__() method - the window isn't fully created until this method has completed. Thus, it hasn't has its width and height set properly.
You could use wx.CallAfter/CallLater to postpone the calling of this function until window creation has fully completed.
I don't know if there is a better solution, but the problem is that when the object was being initialized it didn't have a parent yet, so it didn't know what size it should be. Thus it was width 0 and height 0. However, it needed to initialize the buffer. What I did to fix this was
if size == (0, 0):
size.width = 1
size.height = 1
Once it is added to the frame it gets a new size and the buffer is resized. So I guess that works!
I suppose another solution would be to pass a size parameter to the init method, but i'd prefer not to have to do that if it is not required.
Please post other solutions if you have them =)
I'working on this nice example that shows a webcam output in a GTK widget with python and GStreamer:
http://pygstdocs.berlios.de/pygst-tutorial/webcam-viewer.html
here is the code:
#!/usr/bin/env python
import sys, os
import pygtk, gtk, gobject
import pygst
pygst.require("0.10")
import gst
class GTK_Main:
def __init__(self):
window = gtk.Window(gtk.WINDOW_TOPLEVEL)
window.set_title("Webcam-Viewer")
window.set_default_size(500, 400)
window.connect("destroy", gtk.main_quit, "WM destroy")
vbox = gtk.VBox()
window.add(vbox)
self.movie_window = gtk.DrawingArea()
vbox.add(self.movie_window)
hbox = gtk.HBox()
vbox.pack_start(hbox, False)
hbox.set_border_width(10)
hbox.pack_start(gtk.Label())
self.button = gtk.Button("Start")
self.button.connect("clicked", self.start_stop)
hbox.pack_start(self.button, False)
self.button2 = gtk.Button("Quit")
self.button2.connect("clicked", self.exit)
hbox.pack_start(self.button2, False)
hbox.add(gtk.Label())
window.show_all()
# Set up the gstreamer pipeline
self.player = gst.parse_launch ("v4l2src ! autovideosink")
bus = self.player.get_bus()
bus.add_signal_watch()
bus.enable_sync_message_emission()
bus.connect("message", self.on_message)
bus.connect("sync-message::element", self.on_sync_message)
def start_stop(self, w):
if self.button.get_label() == "Start":
self.button.set_label("Stop")
self.player.set_state(gst.STATE_PLAYING)
else:
self.player.set_state(gst.STATE_NULL)
self.button.set_label("Start")
def exit(self, widget, data=None):
gtk.main_quit()
def on_message(self, bus, message):
t = message.type
if t == gst.MESSAGE_EOS:
self.player.set_state(gst.STATE_NULL)
self.button.set_label("Start")
elif t == gst.MESSAGE_ERROR:
err, debug = message.parse_error()
print "Error: %s" % err, debug
self.player.set_state(gst.STATE_NULL)
self.button.set_label("Start")
def on_sync_message(self, bus, message):
if message.structure is None:
return
message_name = message.structure.get_name()
if message_name == "prepare-xwindow-id":
# Assign the viewport
imagesink = message.src
imagesink.set_property("force-aspect-ratio", True)
imagesink.set_xwindow_id(self.movie_window.window.xid)
GTK_Main()
gtk.gdk.threads_init()
gtk.main()
What I'd like to do is have a method to take a snapshot of the current frame and save to disk.
I think there are 2 ways to do it:
- some gstreamer method (but i think I should at least modify the pipeline)
- grab the picture somehow with GTK itself
Any hint on this?
I have no experience with gstreamer or gtk, any help is really appreciated
Thanks a lot
Mauro
Thanks to OpenCV I managed to rewrite everything with wxPython (which i know better than pyGTK). Here is a full working example (whith snapshot!), if anyone interested.
Also checkout the OpenCV wiki here: http://opencv.willowgarage.com/wiki/wxpython
import wx
import opencv.cv as cv
import opencv.highgui as gui
class CvMovieFrame(wx.Frame):
TIMER_PLAY_ID = 101
def __init__(self, parent):
wx.Frame.__init__(self, parent, -1,)
sizer = wx.BoxSizer(wx.VERTICAL)
self.capture = gui.cvCreateCameraCapture(0)
frame = gui.cvQueryFrame(self.capture)
cv.cvCvtColor(frame, frame, cv.CV_BGR2RGB)
self.SetSize((frame.width + 300, frame.height + 100))
self.bmp = wx.BitmapFromBuffer(frame.width, frame.height, frame.imageData)
self.displayPanel= wx.StaticBitmap(self, -1, bitmap=self.bmp)
sizer.Add(self.displayPanel, 0, wx.ALL, 10)
self.shotbutton = wx.Button(self,-1, "Shot")
sizer.Add(self.shotbutton,-1, wx.GROW)
self.retrybutton = wx.Button(self,-1, "Retry")
sizer.Add(self.retrybutton,-1, wx.GROW)
self.retrybutton.Hide()
#events
self.Bind(wx.EVT_BUTTON, self.onShot, self.shotbutton)
self.Bind(wx.EVT_BUTTON, self.onRetry, self.retrybutton)
self.Bind(wx.EVT_PAINT, self.onPaint)
self.Bind(wx.EVT_CLOSE, self.onClose)
self.playTimer = wx.Timer(self, self.TIMER_PLAY_ID)
wx.EVT_TIMER(self, self.TIMER_PLAY_ID, self.onNextFrame)
self.fps = 8;
self.SetSizer(sizer)
sizer.Layout()
self.startTimer()
def startTimer(self):
if self.fps!=0: self.playTimer.Start(1000/self.fps)#every X ms
else: self.playTimer.Start(1000/15)#assuming 15 fps
def onRetry(self, event):
frame = gui.cvQueryFrame(self.capture)
cv.cvCvtColor(frame, frame, cv.CV_BGR2RGB)
self.bmp = wx.BitmapFromBuffer(frame.width, frame.height, frame.imageData)
self.startTimer()
self.shotbutton.Show()
self.retrybutton.Hide()
self.hasPicture = False
self.Layout()
event.Skip()
def onShot(self, event):
frame = gui.cvQueryFrame(self.capture)
self.playTimer.Stop()
gui.cvSaveImage("foo.png", frame)
self.hasPicture = True
self.shotbutton.Hide()
self.retrybutton.Show()
self.Layout()
event.Skip()
def onClose(self, event):
try:
self.playTimer.Stop()
except:
pass
self.Show(False)
self.Destroy()
def onPaint(self, evt):
if self.bmp:
self.displayPanel.SetBitmap(self.bmp)
evt.Skip()
def onNextFrame(self, evt):
frame = gui.cvQueryFrame(self.capture)
if frame:
cv.cvCvtColor(frame, frame, cv.CV_BGR2RGB)
self.bmp = wx.BitmapFromBuffer(frame.width, frame.height, frame.imageData)
self.Refresh()
evt.Skip()
if __name__=="__main__":
app = wx.App()
f = CvMovieFrame(None)
f.Centre()
f.Show(True)
app.MainLoop()
I'm pretty sure that you could do:
self.movie_window.window.get_image(0, 0, 500, 400)
To get a GtkImage with the last frame from the webcam. The 500 and 400 is the width and the height of the window.