2011-03-25 68 views
10

¿Cómo agregaría acercamiento y alejamiento a la siguiente secuencia de comandos? Me gustaría vincularla a la rueda del mouse. Si está probando este script en Linux, no olvide cambiar el evento MouseWheel a Button-4 y Button-5.¿Cómo agregar y quitar el zoom con un widget Tkinter Canvas?

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

¿Está realmente buscando escalar el lienzo o solo la imagen? –

+2

todo en el lienzo, la imagen y una variedad de líneas y círculos estarán en el lienzo con el tiempo. Y una vez que se coloca, es increíblemente importante que todo mantenga sus coordenadas x, y. – Symon

Respuesta

11

Que yo sepa, la escala de clase incorporada de la lona Tkinter no escalará automáticamente las imágenes. Si no puede usar un widget personalizado, puede escalar la imagen original y reemplazarla en el lienzo cuando se invoca la función de escala.

El siguiente fragmento de código se puede combinar en su clase original. Hace lo siguiente:

  1. Caches resultado de Image.open().
  2. Agrega una función redraw() para calcular la imagen escalada y la agrega al lienzo, y también elimina la imagen dibujada previamente, si existe.
  3. Utiliza las coordenadas del mouse como parte de la ubicación de la imagen. Acabo de pasar x and y a la función create_image para mostrar cómo se desplaza la ubicación de la imagen a medida que se mueve el mouse. Puede reemplazar esto con su propio cálculo de centro/desplazamiento.
  4. Utiliza los botones 4 y 5 de la rueda del mouse de Linux (deberá generalizarlo para que funcione en Windows, etc.).

(Actualizado) Código:

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) 

actualización me hizo un poco de pruebas de diferentes escalas y encontró que un poco de memoria está siendo utilizado por cambio de tamaño/create_image. Ejecuté la prueba usando un JPEG de 540x375 en una Mac Pro con 32 GB de RAM. Esta es la memoria utilizada para diferentes factores de escala:

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 

Teniendo en cuenta lo anterior, una solución más eficiente podría ser la de determinar el tamaño de la ventana donde se mostrará la imagen, calcular un rectángulo de recorte alrededor del centro de la coordenadas del mouse, recorte la imagen usando rect, luego escale solo la parte recortada. Esto debería usar memoria constante para almacenar la imagen temporal. De lo contrario, es posible que necesite usar un control de Tkinter de terceros que realice este recorte de ventana/recorte por usted.

Actualización 2 de trabajo pero la lógica de cultivo muy simplificada, sólo para empezar:

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

No puedo hacer zoom en pasado 2 else me aparece: Runtime Error! Programa: C: \ Python27 \ pythonw.exe Esta aplicación ha solicitado que el Runtime lo termine de una manera inusual. Póngase en contacto con el equipo de soporte de la aplicación para obtener más información. – Symon

+1

@Symon eres bienvenido, me alegro de que te esté ayudando a progresar. Dado que el lienzo no puede cambiar automáticamente el tamaño de las imágenes, el método 'redraw()' lo hace manualmente, volviendo a escalar y volviendo a agregar la imagen al lienzo. Para escalar simultáneamente cualquier vector dibujado en el lienzo (líneas, óvalos, polígonos) puede agregar una llamada a 'self.canvas.scale (ALL, x, y, self.scale, self.scale)' al 'redraw() 'método de arriba. – samplebias

+1

@Symon hmm, estoy usando Linux y no he visto ese error. ¿La imagen que estás escalando es realmente grande? – samplebias

2

podría ser una buena idea para ver el widget TkZinc en lugar de la tela simple para lo que está haciendo, soporta escalado a través de OpenGL.

+0

Miré en TkZinc, sin embargo, si es posible, me gustaría desarrollar la GUI con solo los módulos estándar que vienen con Python 2.7.1 – Symon

6

sólo para el beneficio de otros que encuentran esta pregunta Estoy adjuntando mi código de prueba final Neer que utiliza imagen en imagen/lupa zoom. Básicamente es solo una alteración de las muestras que ya se publicaron. También es genial verlo :).

Como dije antes, si está utilizando este script en Linux, no olvide cambiar el evento MouseWheel a Button-4 y Button-5. Y obviamente necesita insertar una ruta .JPG donde dice "INSERTAR RUTA DE ARCHIVO JPG".

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() 
0
  1. Advanced zoom ejemplo, con base en los azulejos. Al igual que en Google Maps.
  2. Simplified zoom ejemplo, basado en el cambio de tamaño de la imagen completa.
Cuestiones relacionadas