2017-01-14 27 views
9

phụ tùng vải Tkinter đã tích hợp tính năng:Tkinter vải zoom + di chuyển/pan

  • di chuyển/pan vải (ví dụ với Click + Kéo) với canvas.scan_markcanvas.scan_dragto, xem this question

  • phóng to các yếu tố vector trên vải với canvas.scale, nhưng buồn thay, đây doesn't work cho hình ảnh bitmap trên vải

May mắn thay, this method cho phép phóng to hình ảnh (bằng cách vẽ lại thủ công phần thu phóng của hình ảnh). Nhưng:

  1. Như chúng ta đang vẽ lại một phần đặc biệt của vải, di chuyển/Tính năng chảo sẽ không làm việc nữa ...

  2. Chúng tôi hoàn toàn cần phải làm nhiều hơn diện tích đang hiển thị, để cho phép di chuyển/pan. Giả sử chúng ta có bitmap 1000x1000 trên canvas và chúng tôi muốn thu phóng bằng hệ số 50x ... Cách tránh bitmap 50.000 x 50.000 pixel trong bộ nhớ? (2,5 gigapixels trong RAM quá lớn). Chúng tôi có thể nghĩ về việc chỉ hiển thị chế độ xem hoặc nhiều hơn một chút so với chế độ xem hiện tại để cho phép xoay, nhưng sau đó phải làm gì khi việc di chuyển dẫn đến cạnh của vùng được hiển thị?

Cách di chuyển/pan + zoom trên vải Tkinter, hoạt động cho hình ảnh?

+0

Mã đó có vẻ không chính xác. Không có ':' sau câu lệnh 'if' ban đầu, và một số hoặc tất cả các thụt đầu dòng là sai –

+0

Ok, xấu của tôi. Sẽ không bình thường khi có một câu lệnh 'if'. –

+0

Có lẽ đây là trường hợp sử dụng gạch? – user650881

Trả lời

2

Ví dụ thu phóng nâng cao. Giống như trong Google Maps.

Ví dụ video (longer video here):

Nó phóng chỉ một lát, nhưng không phải là toàn bộ hình ảnh. Vì vậy, gạch thu phóng chiếm bộ nhớ liên tục và không nhồi nhét nó với một hình ảnh được thu nhỏ kích thước lớn cho các zoom lớn. Đối với ví dụ thu phóng đơn giản look here.

Đã thử nghiệm trên Windows 7 64 bit và Python 3.6.2.

Đừng quên đặt đường dẫn đến hình ảnh của bạn ở cuối tập lệnh.

# -*- coding: utf-8 -*- 
# Advanced zoom example. Like in Google Maps. 
# It zooms only a tile, but not the whole image. So the zoomed tile occupies 
# constant memory and not crams it with a huge resized image for the large zooms. 
import random 
import tkinter as tk 
from tkinter import ttk 
from PIL import Image, ImageTk 

class AutoScrollbar(ttk.Scrollbar): 
    ''' A scrollbar that hides itself if it's not needed. 
     Works only if you use the grid geometry manager ''' 
    def set(self, lo, hi): 
     if float(lo) <= 0.0 and float(hi) >= 1.0: 
      self.grid_remove() 
     else: 
      self.grid() 
      ttk.Scrollbar.set(self, lo, hi) 

    def pack(self, **kw): 
     raise tk.TclError('Cannot use pack with this widget') 

    def place(self, **kw): 
     raise tk.TclError('Cannot use place with this widget') 

class Zoom_Advanced(ttk.Frame): 
    ''' Advanced zoom of the image ''' 
    def __init__(self, mainframe, path): 
     ''' Initialize the main Frame ''' 
     ttk.Frame.__init__(self, master=mainframe) 
     self.master.title('Zoom with mouse wheel') 
     # Vertical and horizontal scrollbars for canvas 
     vbar = AutoScrollbar(self.master, orient='vertical') 
     hbar = AutoScrollbar(self.master, orient='horizontal') 
     vbar.grid(row=0, column=1, sticky='ns') 
     hbar.grid(row=1, column=0, sticky='we') 
     # Create canvas and put image on it 
     self.canvas = tk.Canvas(self.master, highlightthickness=0, 
           xscrollcommand=hbar.set, yscrollcommand=vbar.set) 
     self.canvas.grid(row=0, column=0, sticky='nswe') 
     self.canvas.update() # wait till canvas is created 
     vbar.configure(command=self.scroll_y) # bind scrollbars to the canvas 
     hbar.configure(command=self.scroll_x) 
     # Make the canvas expandable 
     self.master.rowconfigure(0, weight=1) 
     self.master.columnconfigure(0, weight=1) 
     # Bind events to the Canvas 
     self.canvas.bind('<Configure>', self.show_image) # canvas is resized 
     self.canvas.bind('<ButtonPress-1>', self.move_from) 
     self.canvas.bind('<B1-Motion>',  self.move_to) 
     self.canvas.bind('<MouseWheel>', self.wheel) # with Windows and MacOS, but not Linux 
     self.canvas.bind('<Button-5>', self.wheel) # only with Linux, wheel scroll down 
     self.canvas.bind('<Button-4>', self.wheel) # only with Linux, wheel scroll up 
     self.image = Image.open(path) # open image 
     self.width, self.height = self.image.size 
     self.imscale = 1.0 # scale for the canvaas image 
     self.delta = 1.3 # zoom magnitude 
     # Put image into container rectangle and use it to set proper coordinates to the image 
     self.container = self.canvas.create_rectangle(0, 0, self.width, self.height, width=0) 
     # Plot some optional random rectangles for the test purposes 
     minsize, maxsize, number = 5, 20, 10 
     for n in range(number): 
      x0 = random.randint(0, self.width - maxsize) 
      y0 = random.randint(0, self.height - maxsize) 
      x1 = x0 + random.randint(minsize, maxsize) 
      y1 = y0 + random.randint(minsize, maxsize) 
      color = ('red', 'orange', 'yellow', 'green', 'blue')[random.randint(0, 4)] 
      self.canvas.create_rectangle(x0, y0, x1, y1, fill=color, activefill='black') 
     self.show_image() 

    def scroll_y(self, *args, **kwargs): 
     ''' Scroll canvas vertically and redraw the image ''' 
     self.canvas.yview(*args, **kwargs) # scroll vertically 
     self.show_image() # redraw the image 

    def scroll_x(self, *args, **kwargs): 
     ''' Scroll canvas horizontally and redraw the image ''' 
     self.canvas.xview(*args, **kwargs) # scroll horizontally 
     self.show_image() # redraw the image 

    def move_from(self, event): 
     ''' Remember previous coordinates for scrolling with the mouse ''' 
     self.canvas.scan_mark(event.x, event.y) 

    def move_to(self, event): 
     ''' Drag (move) canvas to the new position ''' 
     self.canvas.scan_dragto(event.x, event.y, gain=1) 
     self.show_image() # redraw the image 

    def wheel(self, event): 
     ''' Zoom with mouse wheel ''' 
     x = self.canvas.canvasx(event.x) 
     y = self.canvas.canvasy(event.y) 
     bbox = self.canvas.bbox(self.container) # get image area 
     if bbox[0] < x < bbox[2] and bbox[1] < y < bbox[3]: pass # Ok! Inside the image 
     else: return # zoom only inside image area 
     scale = 1.0 
     # Respond to Linux (event.num) or Windows (event.delta) wheel event 
     if event.num == 5 or event.delta == -120: # scroll down 
      i = min(self.width, self.height) 
      if int(i * self.imscale) < 30: return # image is less than 30 pixels 
      self.imscale /= self.delta 
      scale  /= self.delta 
     if event.num == 4 or event.delta == 120: # scroll up 
      i = min(self.canvas.winfo_width(), self.canvas.winfo_height()) 
      if i < self.imscale: return # 1 pixel is bigger than the visible area 
      self.imscale *= self.delta 
      scale  *= self.delta 
     self.canvas.scale('all', x, y, scale, scale) # rescale all canvas objects 
     self.show_image() 

    def show_image(self, event=None): 
     ''' Show image on the Canvas ''' 
     bbox1 = self.canvas.bbox(self.container) # get image area 
     # Remove 1 pixel shift at the sides of the bbox1 
     bbox1 = (bbox1[0] + 1, bbox1[1] + 1, bbox1[2] - 1, bbox1[3] - 1) 
     bbox2 = (self.canvas.canvasx(0), # get visible area of the canvas 
       self.canvas.canvasy(0), 
       self.canvas.canvasx(self.canvas.winfo_width()), 
       self.canvas.canvasy(self.canvas.winfo_height())) 
     bbox = [min(bbox1[0], bbox2[0]), min(bbox1[1], bbox2[1]), # get scroll region box 
       max(bbox1[2], bbox2[2]), max(bbox1[3], bbox2[3])] 
     if bbox[0] == bbox2[0] and bbox[2] == bbox2[2]: # whole image in the visible area 
      bbox[0] = bbox1[0] 
      bbox[2] = bbox1[2] 
     if bbox[1] == bbox2[1] and bbox[3] == bbox2[3]: # whole image in the visible area 
      bbox[1] = bbox1[1] 
      bbox[3] = bbox1[3] 
     self.canvas.configure(scrollregion=bbox) # set scroll region 
     x1 = max(bbox2[0] - bbox1[0], 0) # get coordinates (x1,y1,x2,y2) of the image tile 
     y1 = max(bbox2[1] - bbox1[1], 0) 
     x2 = min(bbox2[2], bbox1[2]) - bbox1[0] 
     y2 = min(bbox2[3], bbox1[3]) - bbox1[1] 
     if int(x2 - x1) > 0 and int(y2 - y1) > 0: # show image if it in the visible area 
      x = min(int(x2/self.imscale), self.width) # sometimes it is larger on 1 pixel... 
      y = min(int(y2/self.imscale), self.height) # ...and sometimes not 
      image = self.image.crop((int(x1/self.imscale), int(y1/self.imscale), x, y)) 
      imagetk = ImageTk.PhotoImage(image.resize((int(x2 - x1), int(y2 - y1)))) 
      imageid = self.canvas.create_image(max(bbox2[0], bbox1[0]), max(bbox2[1], bbox1[1]), 
               anchor='nw', image=imagetk) 
      self.canvas.lower(imageid) # set image into background 
      self.canvas.imagetk = imagetk # keep an extra reference to prevent garbage-collection 

path = 'doge.jpg' # place path to your image here 
root = tk.Tk() 
app = Zoom_Advanced(root, path=path) 
root.mainloop() 
+0

Câu trả lời rất rất hay, cảm ơn rất nhiều @foobar! – Basj

+1

Để Basj - Bạn rất hoan nghênh :-) –

1

Bạn có thể xem xét sử dụng lát bản đồ cho trường hợp này. Các ô có thể được cụ thể cho mức thu phóng. Sau khi chọn ô dựa trên mức thu phóng và thu phóng, bạn có thể định vị chúng trên canvas với Canvas.create_image.

Giả sử bạn có một số lớp xếp kề với tọa độ và hình ảnh của nó, bạn có thể chọn cho các ô hiển thị dựa trên kích thước pan, zoom và khung hình.

for tile in visible_tiles(pan_center, frame_dimensions, zoom_level): 
    canvas.create_image(tile.x, tile.y, anchor=Tkinter.NW, image=tile.image) 

Có một ví dụ đầy đủ về điều này trong Tile-Based Geospatial Information Systems bởi John Sample và Elias Ioup trong chương về Khách hàng lập bản đồ được lát gạch.

+0

Có vẻ thú vị, nhưng nguồn này khá đắt (109 $). Bạn có thể chèn một ví dụ mã tối thiểu về cách làm điều đó trong Python? – Basj

+0

Tôi liên kết với một trích đoạn trực tuyến từ cuốn sách trong phản hồi bình luận trước đó của tôi. – user650881