wxPython: any smart way to do an image zoom (mine not working)

Hi,
I tried to understand how can I do a zooming on a image using a Panel. I tried with functions resize (for the image) and blit (to paste from a MemoryDC to the current DC). A problem always occurs when I zoom and I didn't find what is wrong in my code: the image moves very far from the zooming point and I cannot stay centered.
Maybe if I give you a PART of my source code, you will be able to understand what I mean (my script is inspired from the floatCanvas.py file given with wxPython).

Thanks,
Mathieu

class GelCanvas(wxPanel):
    def __init__(self, parent, id=-1, size=wxDefaultSize, item=None):
        wxPanel.__init__(self, parent, id, wxPoint(-1,-1), size, wxSUNKEN_BORDER)
        self.image = None
        self.screenImage = None
# EVT_PAINT(self, self.OnPaint)
        EVT_SIZE(self, self.OnPaint)
        EVT_LEFT_DOWN(self, self.LeftButtonEvent)
        EVT_LEFT_UP(self, self.LeftButtonEvent)
        EVT_RIGHT_DOWN(self, self.RightButtonEvent)
        EVT_MOTION(self, self.LeftButtonEvent)

        self.AspectRatio = 1.0
        self.PanelSize = array(self.GetSize(),Float)
        self.Scale = 1
               self.ViewPortCenter= array( (0,0), Float)
        self.TransformVector = array( (1,1), Float) # default Transformation
        self.GUIMode = None
        self.StartRBBox = None
        self.StartMove = None

    def OnPaint(self, event):
        self.Draw()
           def Draw(self):
        ScreenDC = wxClientDC(self)

        if self.screenImage:
            tmp_dc = wxMemoryDC()
            tmp_dc.BeginDrawing()
            tmp_dc.SelectObject(self.screenImage)
            tmp_dc.EndDrawing()

            ScreenDC.Clear()
            ScreenDC.Blit( 0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight(), tmp_dc,
                           (self.ViewPortCenter[0])*self.TransformVector[0],
                           (self.ViewPortCenter[1])*(self.TransformVector[1]))
           def LeftButtonEvent(self,event):
        if self.GUIMode:
            if self.GUIMode == "ZoomIn":
                if event.LeftDown():
                    self.StartRBBox = (event.GetX(),event.GetY())
                    self.PrevRBBox = None
                elif event.Dragging() and event.LeftIsDown() and self.StartRBBox:
                    x0,y0 = self.StartRBBox
                    x1,y1 = event.GetX(),event.GetY()
                    w, h = abs(x1-x0),abs(y1-y0)
                    w = max(w,int(h*self.AspectRatio))
                    h = int(w/self.AspectRatio)
                    x_c, y_c = (x0+x1)/2 , (y0+y1)/2
                    dc = wxClientDC(self)
                    dc.BeginDrawing()
                    dc.SetPen(wxPen('WHITE', 2,wxSHORT_DASH))
                    dc.SetBrush(wxTRANSPARENT_BRUSH)
                    dc.SetLogicalFunction(wxXOR)
                    if self.PrevRBBox:
                        dc.DrawRectangle(*self.PrevRBBox)
                    dc.DrawRectangle(x_c-w/2,y_c-h/2,w,h)
                    self.PrevRBBox = (x_c-w/2,y_c-h/2,w,h)
                    dc.EndDrawing()
                                   elif event.LeftUp() and self.StartRBBox :
                    EndRBBox = (event.GetX(),event.GetY())
                    StartRBBox = self.StartRBBox
                    if abs(StartRBBox[0] - EndRBBox[0]) > 10 and abs(StartRBBox[1] - EndRBBox[1]) > 10:
                        EndRBBox = self.PixelToWorld(EndRBBox)
                        StartRBBox = self.PixelToWorld(StartRBBox)
                        Center = self.WorldToPixel(abs(StartRBBox-EndRBBox))
                        Factor = (max(EndRBBox[0],StartRBBox[0]) - min(EndRBBox[0],StartRBBox[0])) / (self.image.GetWidth()*self.TransformVector[0])
                        self.Zoom(Factor, Center)
                    else:
                        Center = (StartRBBox)
                        self.Zoom(1.5,Center)
                    self.StartRBBox = None
            if self.GUIMode == "ZoomOut":
                if event.LeftDown():
                    Center = self.WorldToPixel((event.GetX(),event.GetY()))
                    self.Zoom(1/1.5,Center)
            elif self.GUIMode == "Move":
                if event.LeftDown():
                    self.StartMove = array((event.GetX(),event.GetY()))
                    self.PrevMoveBox = None
                elif event.Dragging() and event.LeftIsDown() and self.StartMove:
                    x_1,y_1 = event.GetX(),event.GetY()
                    w, h = self.GetSize()
                    x_tl, y_tl = x_1 - self.StartMove[0], y_1 - self.StartMove[1]
                    dc = wxClientDC(self)
                    dc.BeginDrawing()
                    dc.SetPen(wxPen('WHITE', 1,))
                    dc.SetBrush(wxTRANSPARENT_BRUSH)
                    dc.SetLogicalFunction(wxXOR)
                    if self.PrevMoveBox:
                        dc.DrawRectangle(*self.PrevMoveBox)
                    dc.DrawRectangle(x_tl,y_tl,w,h)
                    self.PrevMoveBox = (x_tl,y_tl,w,h)
                    dc.EndDrawing()
                                 elif event.LeftUp() and self.StartMove:
                    StartMove = self.StartMove
                    EndMove = array((event.GetX(),event.GetY()))
                    if sum((StartMove-EndMove)**2) > 16:
                        self.Move(StartMove-EndMove,'Pixel')
                    self.StartMove = None
    def PixelToWorld(self,Points):
        return (((array(Points,Float) - (self.PanelSize/2))/self.TransformVector) + self.ViewPortCenter)
    def WorldToPixel(self,Coordinates):
       return (((array(Coordinates,Float) - self.ViewPortCenter)*self.TransformVector) + (self.PanelSize/2)).astype('i')
    def PixelToImage(self, Points):
        ViewPortOrigin = self.ViewPortCenter - self.PanelSize/2
        return (int(Points[0]/self.TransformVector[0] + ViewPortOrigin[0]) , int(Points[1]/self.TransformVector[0] - ViewPortOrigin[1]))
    def ImageToPixel(self, Points):
        ViewPortOrigin = (0,0)
        return ((Points[0] - ViewPortOrigin[0])*self.TransformVector[0] , (Points[1] + ViewPortOrigin[1])*self.TransformVector[1])

    def Move(self,shift,CoordType):
        shift = array(shift,Float)
        if CoordType == 'Panel':# convert from panel coordinates
            shift = shift * array((-1,-1),Float) *self.PanelSize/self.TransformVector
        elif CoordType == 'Pixel': # convert from pixel coordinates
            shift = shift/self.TransformVector
        elif CoordType == 'World': # No conversion
            pass
                  self.ViewPortCenter = self.ViewPortCenter + shift
        self.Draw()
           def Zoom(self,factor,center = None):
        self.Scale = self.Scale*factor
        self.TransformVector = array((self.Scale,self.Scale),Float)
        self.screenImage = self.image.Scale(self.image.GetWidth()*self.TransformVector[0],self.image.GetHeight()*self.TransformVector[1]).ConvertToBitmap()
        center = (-(self.GetSize().GetWidth()/2 - center[0]), -(self.GetSize().GetHeight()/2 - center[1]))
        print "center: ", center, " ViewPortCenter", self.ViewPortCenter
        self.Move(center, 'Pixel')
        self.Draw()

Mathieu Drapeau wrote:

Hi,
I tried to understand how can I do a zooming on a image using a Panel. I tried with functions resize (for the image) and blit (to paste from a MemoryDC to the current DC). A problem always occurs when I zoom and I didn't find what is wrong in my code: the image moves very far from the zooming point and I cannot stay centered.
Maybe if I give you a PART of my source code, you will be able to understand what I mean

Part of the code makes it harder. try to wrap it in a stand-alone app so we can test it out. Also, the enclosed code go kind of mangled by your mailer. It's better to put it in as an enclosure.

  (my script is inspired from the floatCanvas.py

file given with wxPython).

Which has been updated substantial, by the way:

http://home.comcast.net/~chrishbarker/FloatCanvas/

I'll be putting out a new release soon also.

A few comments on your code:

   def OnPaint(self, event):

         def Draw(self):
       ScreenDC = wxClientDC(self)

if this is called from an OnPaint event, it should use a wx.PaintDC. Usually, a Draw method takes a DC as a parameter, so it can be used in different ways. Also, FloatCanvas is double buffered, which you might want to do, in which case, OnPaint should just be a blit of the offscreen buffer.

       if self.screenImage:
           tmp_dc = wxMemoryDC()
           tmp_dc.BeginDrawing()
           tmp_dc.SelectObject(self.screenImage)
           tmp_dc.EndDrawing()

you don't need to Begin and EndDrawing here, SelectObject is NOT a drawing method, it's the method that tells the MemoryDC what bitmap to draw to.

           ScreenDC.Clear()

somewhere in here is probably a little math error...

           ScreenDC.Blit( 0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight(), tmp_dc,
                          (self.ViewPortCenter[0])*self.TransformVector[0],
                          
I think you need to do a shift here too..

(self.ViewPortCenter[1])*(self.TransformVector[1]))

this looks OK.

       self.screenImage = self.image.Scale(self.image.GetWidth()*self.TransformVector[0],self.image.GetHeight()*self.TransformVector[1]).ConvertToBitmap()

By the way, if you figure this out, I'd love to have a scalable bitmap object in FloatCanvas. Maybe you could take that approach...rather than pulling code from FloatCanvas and making your own class, just add a scalable bitmap object to FloatCanvas. If you do it hat way. I'll help.

-Chris

···

--
Christopher Barker, Ph.D.
Oceanographer
                                         
NOAA/OR&R/HAZMAT (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker@noaa.gov