Python 使用Tkinter画布小部件添加放大和缩小功能?

Python 使用Tkinter画布小部件添加放大和缩小功能?,python,tkinter,zooming,Python,Tkinter,Zooming,如何将放大和缩小添加到以下脚本中,我想将其绑定到鼠标滚轮。如果要在linux上测试此脚本,请不要忘记将鼠标滚轮事件更改为Button-4和Button-5 from Tkinter import * import Image, ImageTk class GUI: def __init__(self,root): frame = Frame(root, bd=2, relief=SUNKEN) frame.grid_rowconfigure(0, w

如何将放大和缩小添加到以下脚本中,我想将其绑定到鼠标滚轮。如果要在linux上测试此脚本,请不要忘记将鼠标滚轮事件更改为Button-4和Button-5

from Tkinter import * 
import Image, ImageTk

class GUI:
    def __init__(self,root):
        frame = Frame(root, bd=2, relief=SUNKEN)

        frame.grid_rowconfigure(0, weight=1)
        frame.grid_columnconfigure(0, weight=1)
        xscrollbar = Scrollbar(frame, orient=HORIZONTAL)
        xscrollbar.grid(row=1, column=0, sticky=E+W)
        yscrollbar = Scrollbar(frame)
        yscrollbar.grid(row=0, column=1, sticky=N+S)
        self.canvas = Canvas(frame, bd=0, xscrollcommand=xscrollbar.set, yscrollcommand=yscrollbar.set, xscrollincrement = 10, yscrollincrement = 10)
        self.canvas.grid(row=0, column=0, sticky=N+S+E+W)

        File = "PATH TO JPG PICTURE HERE"

        self.img = ImageTk.PhotoImage(Image.open(File))
        self.canvas.create_image(0,0,image=self.img, anchor="nw")
        self.canvas.config(scrollregion=self.canvas.bbox(ALL))
        xscrollbar.config(command=self.canvas.xview)
        yscrollbar.config(command=self.canvas.yview)

        frame.pack()

        self.canvas.bind("<Button 3>",self.grab)
        self.canvas.bind("<B3-Motion>",self.drag)
        root.bind("<MouseWheel>",self.zoom)


    def grab(self,event):
        self._y = event.y
        self._x = event.x

    def drag(self,event):
        if (self._y-event.y < 0): self.canvas.yview("scroll",-1,"units")
        elif (self._y-event.y > 0): self.canvas.yview("scroll",1,"units")
        if (self._x-event.x < 0): self.canvas.xview("scroll",-1,"units")
        elif (self._x-event.x > 0): self.canvas.xview("scroll",1,"units")
        self._x = event.x
        self._y = event.y

    def zoom(self,event):
        if event.delta>0: print "ZOOM IN!"
        elif event.delta<0: print "ZOOM OUT!"


root = Tk()   
GUI(root)
root.mainloop()
从Tkinter导入*
导入图像
类GUI:
定义初始化(自,根):
框架=框架(根部,bd=2,浮雕=凹陷)
frame.grid_rowconfigure(0,权重=1)
frame.grid\u columnconfigure(0,权重=1)
xscrollbar=滚动条(框架,方向=水平)
xscrollbar.grid(行=1,列=0,粘性=E+W)
yscrollbar=滚动条(帧)
yscrollbar.grid(行=0,列=1,粘性=N+S)
self.canvas=canvas(frame,bd=0,xscrollcommand=xscrollbar.set,yscrollcommand=yscrollbar.set,xscrolincrement=10,yscrolincrement=10)
self.canvas.grid(行=0,列=0,粘性=N+S+E+W)
File=“此处JPG图片的路径”
self.img=ImageTk.PhotoImage(Image.open(文件))
self.canvas.create_image(0,0,image=self.img,anchor=“nw”)
self.canvas.config(scrollregion=self.canvas.bbox(全部))
xscrollbar.config(命令=self.canvas.xview)
yscrollbar.config(命令=self.canvas.yview)
frame.pack()
self.canvas.bind(“,self.grab)
self.canvas.bind(“,self.drag)
root.bind(“,self.zoom)
def抓取(自身、事件):
self.\u y=event.y
self._x=事件.x
def拖动(自身、事件):
if(self._y-event.y<0):self.canvas.yview(“滚动”、-1,“单位”)
elif(self._y-event.y>0):self.canvas.yview(“滚动”,1,“单位”)
if(self._x-event.x<0):self.canvas.xview(“滚动”、-1,“单位”)
elif(self._x-event.x>0):self.canvas.xview(“滚动”,1,“单位”)
self._x=事件.x
self.\u y=event.y
def缩放(自身、事件):
如果event.delta>0:打印“放大!”

elif event.delta查看小部件而不是简单的画布可能是一个好主意,它支持通过OpenGL进行缩放。

据我所知,内置的Tkinter画布类缩放不会自动缩放图像。如果无法使用自定义小部件,则可以缩放原始图像,并在调用scale函数时在画布上替换它

下面的代码片段可以合并到原始类中。它做了以下工作:

  • 缓存
    Image.open()
    的结果
  • 添加一个
    redraw()
    函数来计算缩放图像,并将其添加到画布中,同时删除以前绘制的图像(如果有)
  • 使用鼠标坐标作为图像放置的一部分。我只需将
    x和y
    传递到
    create_image
    函数,以显示图像位置在鼠标移动时如何移动。您可以使用自己的中心/偏移计算来替换此值
  • 这使用了Linux鼠标滚轮按钮4和5(您需要将其推广到Windows上,等等)
  • 更新的)代码:

    更新我做了一些不同比例的测试,发现调整/创建图像使用了相当多的内存。我在Mac Pro上使用540x375 JPEG和32GB RAM进行了测试。以下是用于不同比例因子的内存:

     1x  (500,     375)      14 M
     2x  (1000,    750)      19 M
     4x  (2000,   1500)      42 M
     8x  (4000,   3000)     181 M
    16x  (8000,   6000)     640 M
    32x  (16000, 12000)    1606 M
    64x  (32000, 24000)  ...  
    reached around ~7400 M and ran out of memory, EXC_BAD_ACCESS in _memcpy
    
    鉴于上述情况,一个更有效的解决方案可能是确定将显示图像的视口的大小,计算鼠标坐标中心周围的裁剪矩形,使用rect裁剪图像,然后仅缩放裁剪部分。这应该使用恒定内存来存储临时映像。否则,您可能需要使用第三方Tkinter控件来执行此裁剪/窗口缩放

    更新2工作但裁剪逻辑过于简单,只是为了让您开始:

        def redraw(self, x=0, y=0):
            if self.img_id: self.canvas.delete(self.img_id)
            iw, ih = self.orig_img.size
            # calculate crop rect
            cw, ch = iw / self.scale, ih / self.scale
            if cw > iw or ch > ih:
                cw = iw
                ch = ih
            # crop it
            _x = int(iw/2 - cw/2)
            _y = int(ih/2 - ch/2)
            tmp = self.orig_img.crop((_x, _y, _x + int(cw), _y + int(ch)))
            size = int(cw * self.scale), int(ch * self.scale)
            # draw
            self.img = ImageTk.PhotoImage(tmp.resize(size))
            self.img_id = self.canvas.create_image(x, y, image=self.img)
            gc.collect()
    

    为了其他发现这个问题的人的利益,我附上了我的neer最终测试代码,它使用画中画/放大镜缩放。它基本上只是对samplebias已经发布的内容的修改。看到它也很酷:)

    如前所述,如果您在linux上使用此脚本,请不要忘记将鼠标滚轮事件更改为Button-4和Button-5。显然,您需要插入一个.JPG路径,其中显示“insert JPG FILE path”

    从Tkinter导入*
    导入图像
    类LoadImage:
    定义初始化(自,根):
    帧=帧(根)
    self.canvas=canvas(框架,宽度=900,高度=900)
    self.canvas.pack()
    frame.pack()
    File=“插入JPG文件路径”
    self.orig_img=Image.open(文件)
    self.img=ImageTk.PhotoImage(self.orig\u img)
    self.canvas.create_image(0,0,image=self.img,anchor=“nw”)
    self.zoomcycle=0
    self.zimg_id=None
    root.bind(“,self.zoomer)
    self.canvas.bind(“,self.crop)
    def缩放器(自身、事件):
    如果(event.delta>0):
    如果self.zoomcycle!=4:self.zoomcycle+=1
    elif(event.delta<0):
    如果self.zoomcycle!=0:self.zoomcycle-=1
    自裁剪(事件)
    def作物(自身、事件):
    if self.zimg_id:self.canvas.delete(self.zimg_id)
    如果(自身动物循环)!=0:
    x、 y=事件.x,事件.y
    如果self.zoomcycle==1:
    tmp=自身原始图像裁剪((x-45,y-30,x+45,y+30))
    elif self.zoomcycle==2:
    tmp=自原点作物(x-30,y-20,x+30,y+20))
    elif self.zoomcycle==3:
    tmp=自原点作物(x-15,y-10,x+15,y+10))
    elif self.zoomcycle==4:
    tmp=自身原始图像裁剪((x-6,y-4,x+6,y+4))
    尺寸=300200
    self.zimg=ImageTk.PhotoImage(tmp.resize(大小))
    self.zimg_id=self.canvas.create_image(event.x,event.y,image=self.zimg)
    如果uuuu name uuuuuu='\uuuuuuu main\uuuuuuu':
    root=Tk()
    根标题(“作物测试”)
    App=LoadImage(根目录)
    root.mainloop()
    
  • 例如,基于平铺。就像谷歌地图一样
  • <
        def redraw(self, x=0, y=0):
            if self.img_id: self.canvas.delete(self.img_id)
            iw, ih = self.orig_img.size
            # calculate crop rect
            cw, ch = iw / self.scale, ih / self.scale
            if cw > iw or ch > ih:
                cw = iw
                ch = ih
            # crop it
            _x = int(iw/2 - cw/2)
            _y = int(ih/2 - ch/2)
            tmp = self.orig_img.crop((_x, _y, _x + int(cw), _y + int(ch)))
            size = int(cw * self.scale), int(ch * self.scale)
            # draw
            self.img = ImageTk.PhotoImage(tmp.resize(size))
            self.img_id = self.canvas.create_image(x, y, image=self.img)
            gc.collect()
    
    from Tkinter import *
    import Image, ImageTk
    
    class LoadImage:
        def __init__(self,root):
            frame = Frame(root)
            self.canvas = Canvas(frame,width=900,height=900)
            self.canvas.pack()
            frame.pack()
            File = "INSERT JPG FILE PATH"
            self.orig_img = Image.open(File)
            self.img = ImageTk.PhotoImage(self.orig_img)
            self.canvas.create_image(0,0,image=self.img, anchor="nw")
    
            self.zoomcycle = 0
            self.zimg_id = None
    
            root.bind("<MouseWheel>",self.zoomer)
            self.canvas.bind("<Motion>",self.crop)
    
        def zoomer(self,event):
            if (event.delta > 0):
                if self.zoomcycle != 4: self.zoomcycle += 1
            elif (event.delta < 0):
                if self.zoomcycle != 0: self.zoomcycle -= 1
            self.crop(event)
    
        def crop(self,event):
            if self.zimg_id: self.canvas.delete(self.zimg_id)
            if (self.zoomcycle) != 0:
                x,y = event.x, event.y
                if self.zoomcycle == 1:
                    tmp = self.orig_img.crop((x-45,y-30,x+45,y+30))
                elif self.zoomcycle == 2:
                    tmp = self.orig_img.crop((x-30,y-20,x+30,y+20))
                elif self.zoomcycle == 3:
                    tmp = self.orig_img.crop((x-15,y-10,x+15,y+10))
                elif self.zoomcycle == 4:
                    tmp = self.orig_img.crop((x-6,y-4,x+6,y+4))
                size = 300,200
                self.zimg = ImageTk.PhotoImage(tmp.resize(size))
                self.zimg_id = self.canvas.create_image(event.x,event.y,image=self.zimg)
    
    if __name__ == '__main__':
        root = Tk()
        root.title("Crop Test")
        App = LoadImage(root)
        root.mainloop()