2011-03-25 238 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()