Problem with BufferedDC

Hi all,

I am just a beginner in wxPython programming. I want to rotate a PNG-Picture in
small steps cw and ccw to align it to an overlayed grid. The grid has to be
drawn over the bitmap everytime. I want to do that flicker free with the
'wx.BufferedPaintDC'-methode.

At the moment I can not say where the program will end up, so I try to do the
whole code according to the MVC (Model View Controller)-structure to keep it
upgradable without loosing the overview. I do not have the experience now to
understand if this is the best way for this project. It is my first GUI-software.

The Problem:
If I click on the [ROTATE RIGHT] or the [ROTATE LEFT]-Button the
'UpdateImageView'-methode is called correctly by the PubSub-System. But the
picture will not be updated. I see only a small black square on the upper left
corner. I can't help myself, I did not find any mistake. Maybe someone can give
me a hint? General comments to the code are also very welcome.

Thank you in advance.
DirkP

Hint: to run the code it is needed have a picture "testpic.png" (400x300 px) in
the current directory.

#!/usr/bin/env python

"""
Rotates image right or left
"""
import wx
from wx.lib.pubsub import Publisher as pub
     
class ImageWork (object):
    def __init__(self):
        self.rot_angle_total = 0.0
       
    def ImgLoad (self):
        self.img = wx.Image('testpic.png', wx.BITMAP_TYPE_PNG)
        return self.img
       
    def ImgRotate (self, dir, image):
        self.img = image
        self.img_centre = wx.Point(self.img.GetWidth()/2,self.img.GetHeight()/2 )
        if dir == "right":
            rot_angle_incr=-0.01
        else:
            rot_angle_incr=0.01
       
        self.rot_angle_total = self.rot_angle_total+rot_angle_incr
        self.img =
self.img.Rotate(self.rot_angle_total,self.img_centre,interpolating=True)
        print self.rot_angle_total
        pub.sendMessage("IMAGE CHANGED", self.img)
        return self.img

class ImageView(wx.Frame):
    def __init__(self, parent, image):
        wx.Frame.__init__(self, parent, title="ImageView", size=(450,450))
        self.img_view = image
        img_window = PaintWindow(self, 1, self.img_view)

    def UpdateImageView(self, image):
        self.img_view = image
        img_window = PaintWindow(self, 1, self.img_view)

class PaintWindow(wx.Window):
    def __init__(self, parent, ID, image):
        wx.Window.__init__(self, parent)
        self.bmp = image.ConvertToBitmap()
        self.Bind(wx.EVT_SIZE, self.OnSize)
        self.Bind(wx.EVT_PAINT, self.OnPaint)

    def OnSize(self, evt):
        self.InitBuffer()
       
    def OnPaint(self, evt):
        dc = wx.BufferedPaintDC(self, self.buffer)
     
    def InitBuffer(self):
        w, h = self.GetClientSize()
        self.buffer = wx.EmptyBitmap(w, h)
        dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)
        dc.Clear()
        self.DrawView(dc)
       
    def DrawView(self, dc):
        dc.DrawBitmap(self.bmp, 1, 1, True)
        dc.SetBrush(wx.TRANSPARENT_BRUSH)
        dc.DrawRectangle(10,10, 380, 270)
        dc = wx.BufferedPaintDC(self, self.buffer)
       
class ControlpanelView(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent, title="Control Panel",size=(200,200))
        sizer = wx.BoxSizer(wx.VERTICAL)
        self.rotright_btn = wx.Button(self, label="ROTATE RIGHT")
        self.rotleft_btn = wx.Button(self, label="ROTATE LEFT")
        sizer.Add(self.rotright_btn, 0, wx.EXPAND | wx.ALL)
        sizer.Add(self.rotleft_btn, 0, wx.EXPAND | wx.ALL)
        self.SetSizer(sizer)

class Controller:
    def __init__(self, app):
        self.imagework = ImageWork()
        self.img=self.imagework.ImgLoad()

        self.imagewindow = ImageView(None, self.img)
        self.controlwindow = ControlpanelView(self.imagewindow)
        self.controlwindow.rotright_btn.Bind(wx.EVT_BUTTON, self.RotRight)
        self.controlwindow.rotleft_btn.Bind(wx.EVT_BUTTON, self.RotLeft)

        pub.subscribe(self.ImageChanged, "IMAGE CHANGED")

        self.imagewindow.Show()
        self.controlwindow.Show()

    def RotRight (self, event):
        self.img = self.imagework.ImgRotate("right", self.img)

    def RotLeft(self, event):
        self.img = self.imagework.ImgRotate("left", self.img)
       
    def ImageChanged(self, message):
        self.imagewindow.UpdateImageView(message.data)

if __name__ == "__main__":
    app = wx.App(False)
    controller = Controller(app)
    app.MainLoop()

You are creating a new instance of the PaintWindow each time you do the update. There is no need to do that, just give the existing window the new image and tell it to refresh itself.

···

On 10/7/11 12:31 PM, DirkP wrote:

Hi all,

I am just a beginner in wxPython programming. I want to rotate a PNG-Picture in
small steps cw and ccw to align it to an overlayed grid. The grid has to be
drawn over the bitmap everytime. I want to do that flicker free with the
'wx.BufferedPaintDC'-methode.

At the moment I can not say where the program will end up, so I try to do the
whole code according to the MVC (Model View Controller)-structure to keep it
upgradable without loosing the overview. I do not have the experience now to
understand if this is the best way for this project. It is my first GUI-software.

The Problem:
If I click on the [ROTATE RIGHT] or the [ROTATE LEFT]-Button the
'UpdateImageView'-methode is called correctly by the PubSub-System. But the
picture will not be updated. I see only a small black square on the upper left
corner. I can't help myself, I did not find any mistake. Maybe someone can give
me a hint? General comments to the code are also very welcome.

     def UpdateImageView(self, image):
         self.img_view = image
         img_window = PaintWindow(self, 1, self.img_view)

--
Robin Dunn
Software Craftsman

> def UpdateImageView(self, image):
> self.img_view = image
> img_window = PaintWindow(self, 1, self.img_view)

You are creating a new instance of the PaintWindow each time you do the
update. There is no need to do that, just give the existing window the
new image and tell it to refresh itself.

Hello Robin,

thank for your hint. I think I understand what you mean but unfortunately I am
not experienced enough to translate it into the code directly. So maybe I have
to ask some stupid questions.

Do you mean I do not need the UpdateImageView-method at all? In the code below I
try to call the ImageView (with the rotated picure) again inside of the
ImageChanged-method (instead of the UpdateImageView). But nothing happens on the
screen. You mentioned refresh, but how can do I do that?

Thank you for your patience.

DirkP

#!/usr/bin/env python

"""
Rotates image right or left
"""
import wx
from wx.lib.pubsub import Publisher as pub
     
class ImageWork (object):
    def __init__(self):
        self.rot_angle_total = 0.0
        
    def ImgLoad (self):
        self.img = wx.Image('testpic.png', wx.BITMAP_TYPE_PNG)
        return self.img
        
    def ImgRotate (self, dir, image):
        self.img = image
        self.img_centre = wx.Point(self.img.GetWidth()/2,self.img.GetHeight()/2 )
        if dir == "right":
            rot_angle_incr=-0.01
        else:
            rot_angle_incr=0.01
        
        self.rot_angle_total = self.rot_angle_total+rot_angle_incr
        self.img =
self.img.Rotate(self.rot_angle_total,self.img_centre,interpolating=True)
        print self.rot_angle_total
        pub.sendMessage("IMAGE CHANGED", self.img)
        return self.img

class ImageView(wx.Frame):
    def __init__(self, parent, image):
        wx.Frame.__init__(self, parent, title="ImageView", size=(450,450))
        self.img_view = image
        img_window = PaintWindow(self, 1, self.img_view)

class PaintWindow(wx.Window):
    def __init__(self, parent, ID, image):
        wx.Window.__init__(self, parent)
        self.bmp = image.ConvertToBitmap()
        self.Bind(wx.EVT_SIZE, self.OnSize)
        self.Bind(wx.EVT_PAINT, self.OnPaint)
        print "PaintWindow"

    def OnSize(self, evt):
        self.InitBuffer()
        
    def OnPaint(self, evt):
        dc = wx.BufferedPaintDC(self, self.buffer)
     
    def InitBuffer(self):
        w, h = self.GetClientSize()
        self.buffer = wx.EmptyBitmap(w, h)
        dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)
        dc.Clear()
        self.DrawView(dc)
        
    def DrawView(self, dc):
        dc.DrawBitmap(self.bmp, 1, 1, True)
        dc.SetBrush(wx.TRANSPARENT_BRUSH)
        dc.DrawRectangle(10,10, 380, 270)
        dc = wx.BufferedPaintDC(self, self.buffer)
                
class ControlpanelView(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent, title="Control Panel",size=(200,200))
        sizer = wx.BoxSizer(wx.VERTICAL)
        self.rotright_btn = wx.Button(self, label="ROTATE RIGHT")
        self.rotleft_btn = wx.Button(self, label="ROTATE LEFT")
        sizer.Add(self.rotright_btn, 0, wx.EXPAND | wx.ALL)
        sizer.Add(self.rotleft_btn, 0, wx.EXPAND | wx.ALL)
        self.SetSizer(sizer)

class Controller:
    def __init__(self, app):
        self.imagework = ImageWork()
        self.img=self.imagework.ImgLoad()
  
        self.imagewindow = ImageView(None, self.img)
        self.controlwindow = ControlpanelView(self.imagewindow)
        self.controlwindow.rotright_btn.Bind(wx.EVT_BUTTON, self.RotRight)
        self.controlwindow.rotleft_btn.Bind(wx.EVT_BUTTON, self.RotLeft)

        pub.subscribe(self.ImageChanged, "IMAGE CHANGED")

        self.imagewindow.Show()
        self.controlwindow.Show()

    def RotRight (self, event):
        self.img = self.imagework.ImgRotate("right", self.img)

    def RotLeft(self, event):
        self.img = self.imagework.ImgRotate("left", self.img)
        
    def ImageChanged(self, message):
        self.imagewindow = ImageView(None, message.data)
        
if __name__ == "__main__":
    app = wx.App(False)
    controller = Controller(app)
    app.MainLoop()

      def UpdateImageView(self, image):
          self.img_view = image
          img_window = PaintWindow(self, 1, self.img_view)

You are creating a new instance of the PaintWindow each time you do the
update. There is no need to do that, just give the existing window the
new image and tell it to refresh itself.

Hello Robin,

thank for your hint. I think I understand what you mean but unfortunately I am
not experienced enough to translate it into the code directly. So maybe I have
to ask some stupid questions.

Do you mean I do not need the UpdateImageView-method at all?

No. I'm saying that instead of creating new instances of the window in order to display the new image that you should instead just tell the existing window to display a new image.

In the code below I
try to call the ImageView (with the rotated picure) again

"Calling" ImageView makes a new window, since ImageView is a class derived from wx.Frame.

inside of the
ImageChanged-method (instead of the UpdateImageView). But nothing happens on the
screen.

Because you never .Show() the new window. But since you don't really want a new window then that doesn't matter.

You mentioned refresh, but how can do I do that?

So to implement what I'm suggesting you need to save a reference to the PaintWindow so you can tell it to paint a different image (the rotated one), and then provide a way to give it that new image to paint. In the attached I've just added methods to ImageView and PaintWindow that are called from ImageChanged(). Another possibility is to just subscribe the PaintWindow to the "IMAGE CHANGED" message, and pass the new image in the data payload of the pubsub message.

imgrot.py (3.61 KB)

···

On 10/9/11 10:51 AM, DirkP wrote:

--
Robin Dunn
Software Craftsman

Hello Robin,
Thank you again!Now I think it does what it was supposed to do.
I also made some more little changes to get it running properly.
Every beginning is hard.

kind regards
DirkP

#!/usr/bin/env python

"""
Rotates image right or left
"""
import wx
from wx.lib.pubsub import Publisher as pub
     
class ImageWork (object):
    def __init__(self):
        self.rot_angle_total = 0.0
        
    def ImgLoad (self):
        self.img = wx.Image('testpic.png', wx.BITMAP_TYPE_PNG)
        return self.img
        
    def ImgRotate (self, dir, image):
        self.img = image
        self.img_centre = wx.Point(self.img.GetWidth()/2,self.img.GetHeight()/2
)
        if dir == "right":
            rot_angle_incr=-0.01
        else:
            rot_angle_incr=0.01
        
        self.rot_angle_total = self.rot_angle_total+rot_angle_incr
        self.img_rot =
self.img.Rotate(self.rot_angle_total,self.img_centre,interpolating=True)
        print self.rot_angle_total
        pub.sendMessage("IMAGE CHANGED", self.img_rot)
        
class ImageView(wx.Frame):
    def __init__(self, parent, image):
        wx.Frame.__init__(self, parent, title="ImageView", size=(450,450))
        self.img_view = image
        self.img_window = PaintWindow(self, 1, self.img_view)

    def SetNewImage(self, img):
        self.img_window.SetNewImage(img)

class PaintWindow(wx.Window):
    def __init__(self, parent, ID, image):
        wx.Window.__init__(self, parent)
        self.SetNewImage(image)
        self.Bind(wx.EVT_SIZE, self.OnSize)
        self.Bind(wx.EVT_PAINT, self.OnPaint)

    def OnSize(self, evt):
        self.InitBuffer()
        
    def OnPaint(self, evt):
        if not hasattr(self, 'buffer'):
            self.InitBuffer()
        dc = wx.BufferedPaintDC(self, self.buffer)
     
    def InitBuffer(self):
        w, h = self.GetClientSize()
        self.buffer = wx.EmptyBitmap(w, h)
        dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)
        dc.Clear()
        self.DrawView(dc)
        
    def DrawView(self, dc):
        cw, ch = self.GetClientSize()
        iw, ih = self.bmp.GetSize()
        xpos = cw/2-iw/2
        ypos = ch/2-ih/2
        dc.DrawBitmap(self.bmp, xpos, ypos, True)
        dc.SetBrush(wx.TRANSPARENT_BRUSH)
        rw = 380
        rh = 270
        xpos_rec = cw/2-rw/2
        ypos_rec = ch/2-rh/2
        dc.DrawRectangle(xpos_rec, ypos_rec, 380, 270)
        
    def SetNewImage(self, img):
        self.bmp = img.ConvertToBitmap()
        self.InitBuffer()
        self.Refresh()
        
class ControlpanelView(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent, title="Control Panel",size=(200,200))
        sizer = wx.BoxSizer(wx.VERTICAL)
        self.rotright_btn = wx.Button(self, label="ROTATE RIGHT")
        self.rotleft_btn = wx.Button(self, label="ROTATE LEFT")
        sizer.Add(self.rotright_btn, 0, wx.EXPAND | wx.ALL)
        sizer.Add(self.rotleft_btn, 0, wx.EXPAND | wx.ALL)
        self.SetSizer(sizer)

class Controller:
    def __init__(self, app):
        self.imagework = ImageWork()
        self.img=self.imagework.ImgLoad()
  
        self.imagewindow = ImageView(None, self.img)
        self.controlwindow = ControlpanelView(self.imagewindow)
        self.controlwindow.rotright_btn.Bind(wx.EVT_BUTTON, self.RotRight)
        self.controlwindow.rotleft_btn.Bind(wx.EVT_BUTTON, self.RotLeft)

        pub.subscribe(self.ImageChanged, "IMAGE CHANGED")

        self.imagewindow.Show()
        self.controlwindow.Show()

    def RotRight (self, event):
        self.imagework.ImgRotate("right", self.img)

    def RotLeft(self, event):
        self.imagework.ImgRotate("left", self.img)
        
    def ImageChanged(self, message):
        self.imagewindow.SetNewImage(message.data)

if __name__ == "__main__":
    app = wx.App(False)
    controller = Controller(app)
    app.MainLoop()