Search code examples
python-3.xwxpythonwxwidgetsscaling

Picture zooming doesnt select the right area


I have an application that displays images. Since people need to read some information out of the image i implemented a zoom functionality.

I set the picture Widget to 600x600. To Preserve the aspect ratio i then scale the picture and draw it to the widget. This works really well.

For the zoom functionality the user should click anywhere on the picture and it should cut out the are 150x150 pixesl around where the cursor clicks. To be precises the click of the cursore should mark the middle of the rectangle i cut out. So if i click on x=300 y=300 the area should be x=225 y=225 width=150 height=150.

To Archive that i scale the coordinates where the user clicks back to the original image resolution, cut out the subimage and scale it back down. Cutting out the scaled image allready loaded in my programm would yield a far worse quality.

The error is simple. The area cut out is not exactly the aerea i would like to cut out. Sometimes it is to far left. Sometimes to far right. Somtes to high sometimes to low... And i fail to see where the problem lies.

I wrote a barebone prototype whith just the functionality needed. After you put in a path to a jpeg picture you should be able to run it.

# -*- coding: utf-8 -*-
"""
Created on Sun Jan 12 12:22:25 2020

@author: Paddy
"""

import wx


class ImageTest(wx.App):
    def __init__(self, redirect=False, filename=None):
        wx.App.__init__(self, redirect, filename)

        self.frame = wx.Frame(None, title='Radsteuer Eintreiber')   

        self.panelleft = wx.Panel(self.frame)
        self.picturepresent=False
        self.index=0
        self.PhotoMaxSize = 600
        self.zoomed=False
        #Change path here
        self.imagepath='F:\Geolocation\Test\IMG_20191113_174257.jpg'
        self.createUI()
        self.frame.Centre()
        self.frame.Show()
        self.frame.Raise()
        self.onView()
    #Creates UI Elements on Initiation    
    def createUI(self):
        #instructions = 'Bild'
        img = wx.Image(self.PhotoMaxSize,self.PhotoMaxSize,clear=True)
        self.imageCtrl = wx.StaticBitmap(self.panelleft, wx.ID_ANY, 
                                         wx.Bitmap(img),size=(self.PhotoMaxSize,self.PhotoMaxSize))
        self.mainSizer = wx.BoxSizer(wx.VERTICAL)        

        self.imageCtrl.Bind(wx.EVT_LEFT_UP, self.onImageClick)     


        self.mainSizer.Add(self.imageCtrl, 0, wx.ALL|wx.ALIGN_CENTER, 5)

        self.panelleft.SetSizer(self.mainSizer)
        self.mainSizer.Fit(self.frame)

        self.panelleft.Layout()




    def onImageClick(self,event):
        if self.zoomed:
            self.onView()
            self.zoomed=False
        else :
            #Determin position of mouse
            ctrl_pos = event.GetPosition()
            print(ctrl_pos)
            picturecutof=self.PhotoMaxSize/4
            if ctrl_pos[0]-((self.PhotoMaxSize-self.NewW)/2)>0:
                xpos=ctrl_pos[0]-((self.PhotoMaxSize-self.NewW)/2)
            else:
                xpos=0                
            if ctrl_pos[0]+picturecutof>self.NewW:
                xpos=self.NewW-picturecutof


            if ctrl_pos[1]-((self.PhotoMaxSize-self.NewW)/2)>0:
                ypos=ctrl_pos[1]-((self.PhotoMaxSize-self.NewW)/2)
            else:
                ypos=0                
            if ctrl_pos[1]+picturecutof>self.NewH:
                ypos=self.NewH-picturecutof


            xpos=xpos*self.W/self.NewW    
            ypos=ypos*self.H/self.NewH
            picturecutofx=picturecutof*self.W/self.NewW
            picturecutofy=picturecutof*self.H/self.NewH

            rectangle=wx.Rect(xpos,ypos,picturecutofx,picturecutofy)
            self.img = wx.Image(self.imagepath, wx.BITMAP_TYPE_ANY)
            self.img=self.img.GetSubImage(rectangle)

            self.img=self.img.Scale(600,600,wx.IMAGE_QUALITY_BICUBIC)
            self.imageCtrl.SetBitmap(wx.Bitmap(self.img))
            self.imageCtrl.Fit()
            self.panelleft.Refresh()
            self.zoomed=True

    def onView(self,event=None):


        self.img = wx.Image(self.imagepath, wx.BITMAP_TYPE_ANY)
        # scale the image, preserving the aspect ratio
        self.W = self.img.GetWidth()
        self.H = self.img.GetHeight()

        if self.W > self.H:
            self.NewW = self.PhotoMaxSize
            self.NewH = self.PhotoMaxSize * self.H / self.W
        else:
            self.NewH = self.PhotoMaxSize 
            self.NewW = self.PhotoMaxSize * self.W / self.H

        self.img = self.img.Scale(self.NewW,self.NewH,wx.IMAGE_QUALITY_BICUBIC)        
        self.imageCtrl.SetBitmap(wx.Bitmap(self.img))
        self.imageCtrl.Fit()
        self.panelleft.Refresh()

if __name__ == '__main__':

    app = ImageTest()
    app.MainLoop()   

There maybe some wird stuff happening in the code that doesnt make entirely sense. Most of it is because the normal programm is much bigger and i removed many features in the prototpy that are having nothing to do with the zooming. It might very well be that i do the scaling wrong. But im out of ideas.

The Functionality for this protopye is simple: Replace the Path to a jpge image of your choiche. Run the programm. Click on the image and it should zoom. Click around your image and you will see zooming is wrong.

Thats it. Thanks for your help.


Solution

  • So i found the answer. But i changed something on the logic also. The picture will now be centered at the position where the user clicked. This is much more intuitive to use. I onle post the onImageClick Function. If you want to use the whole thing feel free to replace it in the original code from the question.

    def onImageClick(self,event):
        if self.zoomed:
            self.onView()
            self.zoomed=False
        else :
            #Determin position of mouse
            ctrl_pos = event.GetPosition()
    
            #Set magnification.
            scalingfactor=4       
    
            #Set Picture size for rectangle            
            picturecutof=self.PhotoMaxSize/scalingfactor
    
            ##Find coordinates by adjusting for picture position
            xpos=ctrl_pos[0]-((self.PhotoMaxSize-self.NewW)/2) 
            ypos=ctrl_pos[1]-((self.PhotoMaxSize-self.NewH)/2)
    
            #if position is out of range adjust
            if xpos>self.NewW:
                xpos=self.NewW
            if xpos<0:
                xpos=0
    
            if ypos>self.NewH:
                ypos=self.NewH
            if ypos<0:
                ypos=0
    
    
            #scale rectangle area to size of the unscaled image
            picturecutofx=picturecutof*self.W/self.NewW            
            picturecutofy=picturecutof*self.H/self.NewH
    
    
    
            #scale coordinates to unscaled image
            xpos=xpos*self.W/self.NewW
            ypos=ypos*self.H/self.NewH
    
            #centeres image onto the coordinates where they were clicked
            xpos=xpos-((ctrl_pos[0]*self.W/self.NewW)/scalingfactor)
            ypos=ypos-((ctrl_pos[1]*self.H/self.NewH)/scalingfactor)
    
    
            #if position is out of range adjust
            if xpos>self.W-picturecutofx:
                xpos=self.W-picturecutofx-5
            if xpos<0:
                xpos=0
    
            if ypos>self.H-picturecutofy:
                ypos=self.H-picturecutofy-5
            if ypos<0:
                ypos=0
    
            #create rectangle to cut from original image
            rectangle=wx.Rect(xpos,ypos,picturecutofx,picturecutofy)
            #load original image again
            self.img = wx.Image(self.imagepath, wx.BITMAP_TYPE_ANY)
            #get subimage 
            self.img=self.img.GetSubImage(rectangle)
    
            #scale subimage to picture area
            self.img=self.img.Scale(self.PhotoMaxSize,self.PhotoMaxSize,wx.IMAGE_QUALITY_BICUBIC)
            self.imageCtrl.SetBitmap(wx.Bitmap(self.img))
            self.imageCtrl.Fit()
            self.panelleft.Refresh()
            self.zoomed=True
    
        event.Skip()