2011-03-25 235 views
10

如何将缩放添加到以下脚本中,我想将它绑定到鼠标滚轮。如果你在linux上测试这个脚本,不要忘记把MouseWheel事件改为Button-4和Button-5。使用Tkinter Canvas Widget添加放大和缩小?

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() 
+0

你真的想缩放画布或只是图像? – 2011-04-11 14:04:13

+2

画布上的所有东西,图像以及各种线条和圆圈最终都会放在画布上。一旦放置,所有事物都保持其x,y坐标是非常重要的。 – Symon 2011-04-11 14:06:04

回答

11

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

下面的代码片段可以合并到您的原始类中。它执行以下操作:

  1. 缓存Image.open()的结果。
  2. 添加一个redraw()函数来计算缩放的图像,并将其添加到画布,并删除以前绘制的图像(如果有的话)。
  3. 将鼠标坐标用作图像放置的一部分。我只是通过x and ycreate_image函数来显示随着鼠标移动图像位置如何转移。您可以将其替换为您自己的中心/偏移量计算。
  4. 这使用Linux鼠标滚轮按钮4和5(您需要推广它在Windows上工作等)。

更新)代码:

class GUI: 
    def __init__(self, root): 

     # ... omitted rest of initialization code 

     self.canvas.config(scrollregion=self.canvas.bbox(ALL)) 
     self.scale = 1.0 
     self.orig_img = Image.open(File) 
     self.img = None 
     self.img_id = None 
     # draw the initial image at 1x scale 
     self.redraw() 

     # ... rest of init, bind buttons, pack frame 

    def zoom(self,event): 
     if event.num == 4: 
      self.scale *= 2 
     elif event.num == 5: 
      self.scale *= 0.5 
     self.redraw(event.x, event.y) 

    def redraw(self, x=0, y=0): 
     if self.img_id: 
      self.canvas.delete(self.img_id) 
     iw, ih = self.orig_img.size 
     size = int(iw * self.scale), int(ih * self.scale) 
     self.img = ImageTk.PhotoImage(self.orig_img.resize(size)) 
     self.img_id = self.canvas.create_image(x, y, image=self.img) 

     # tell the canvas to scale up/down the vector objects as well 
     self.canvas.scale(ALL, x, y, self.scale, self.scale) 

更新我做了一些测试,为不同规模,发现相当多的内存被使用调整大小/ create_image。我在带有32GB RAM的Mac Pro上使用540x375 JPEG进行测试。下面是用于不同的比例系数的存储器:鉴于上述

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 

,更高效的解决方案可能是确定在图像将被显示的视口的大小,计算围绕的中心的裁剪矩形鼠标坐标,使用矩形裁剪图像,然后缩放裁剪的部分。这应该使用常量内存来存储临时图像。否则,您可能需要使用第三方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() 
+0

我无法放大过去2,否则我得到:运行时错误!程序:C:\ Python27 \ pythonw.exe此应用程序已请求Runtime以不寻常的方式终止它,请联系应用程序支持团队以获取更多信息。 – Symon 2011-04-12 14:42:30

+1

@Symon欢迎您,很高兴它帮助您取得进展。考虑到画布无法自动调整图像大小,'redraw()'方法会手动执行 - 重新缩放并将图像重新添加到画布。要同时缩放在画布上绘制的任何矢量(线,椭圆,多边形),您可以将'self.canvas.scale(ALL,x,y,self.scale,self.scale)'调用添加到''redraw() '上面的方法。 – samplebias 2011-04-12 14:51:10

+1

@Symon嗯,我正在使用Linux,并没有看到这个错误。你放大的图像真的很大吗? – samplebias 2011-04-12 14:54:25

2

可能是一个好主意,看看TkZinc小部件,而不是简单的画布,你在做什么,它支持通过OpenGL缩放。

+0

我看了一下TkZinc,但是如果可能的话,我想开发只有Python 2.7.1附带的标准模块的GUI。 – Symon 2011-04-11 21:35:24

6

就为了谁发现了这个问题,我附上我的名义有效汇率最终的测试代码,使用画中画其他的利益/放大镜缩放。它基本上只是改变了已经发布的samplebias。也很酷,看到以及:)。

正如我之前所说的,如果您在Linux上使用此脚本,请不要忘记将MouseWheel事件更改为Button-4和Button-5。而且你显然需要插入一个.JPG路径,它表示“INSERT JPG FILE PATH”。

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()