高级缩放示例.就像谷歌地图一样.
它仅缩放图块,但不缩放整个图像.因此,缩放的瓷砖占据了恒定的记忆,并且不会为大型缩放图像调整大小的图像.对于简化的缩放示例look here.
在Windows 7 64位和Python 3.6.2上测试.
不要忘记在脚本末尾放置图像路径.
# -*- 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) <&#61; 0.0 and float(hi) >&#61; 1.0:
self.grid_remove()
else:
self.grid()
ttk.Scrollbar.set(self, lo, hi)
def pack(self, **kw):
raise tk.TclError(&#39;Cannot use pack with this widget&#39;)
def place(self, **kw):
raise tk.TclError(&#39;Cannot use place with this widget&#39;)
class Zoom_Advanced(ttk.Frame):
&#39;&#39;&#39; Advanced zoom of the image &#39;&#39;&#39;
def __init__(self, mainframe, path):
&#39;&#39;&#39; Initialize the main Frame &#39;&#39;&#39;
ttk.Frame.__init__(self, master&#61;mainframe)
self.master.title(&#39;Zoom with mouse wheel&#39;)
# Vertical and horizontal scrollbars for canvas
vbar &#61; AutoScrollbar(self.master, orient&#61;&#39;vertical&#39;)
hbar &#61; AutoScrollbar(self.master, orient&#61;&#39;horizontal&#39;)
vbar.grid(row&#61;0, column&#61;1, sticky&#61;&#39;ns&#39;)
hbar.grid(row&#61;1, column&#61;0, sticky&#61;&#39;we&#39;)
# Create canvas and put image on it
self.canvas &#61; tk.Canvas(self.master, highlightthickness&#61;0,
xscrollcommand&#61;hbar.set, yscrollcommand&#61;vbar.set)
self.canvas.grid(row&#61;0, column&#61;0, sticky&#61;&#39;nswe&#39;)
self.canvas.update() # wait till canvas is created
vbar.configure(command&#61;self.scroll_y) # bind scrollbars to the canvas
hbar.configure(command&#61;self.scroll_x)
# Make the canvas expandable
self.master.rowconfigure(0, weight&#61;1)
self.master.columnconfigure(0, weight&#61;1)
# Bind events to the Canvas
self.canvas.bind(&#39;&#39;, self.show_image) # canvas is resized
self.canvas.bind(&#39;&#39;, self.move_from)
self.canvas.bind(&#39;&#39;, self.move_to)
self.canvas.bind(&#39;&#39;, self.wheel) # with Windows and MacOS, but not Linux
self.canvas.bind(&#39;&#39;, self.wheel) # only with Linux, wheel scroll down
self.canvas.bind(&#39;&#39;, self.wheel) # only with Linux, wheel scroll up
self.image &#61; Image.open(path) # open image
self.width, self.height &#61; self.image.size
self.imscale &#61; 1.0 # scale for the canvaas image
self.delta &#61; 1.3 # zoom magnitude
# Put image into container rectangle and use it to set proper coordinates to the image
self.container &#61; self.canvas.create_rectangle(0, 0, self.width, self.height, width&#61;0)
# Plot some optional random rectangles for the test purposes
minsize, maxsize, number &#61; 5, 20, 10
for n in range(number):
x0 &#61; random.randint(0, self.width - maxsize)
y0 &#61; random.randint(0, self.height - maxsize)
x1 &#61; x0 &#43; random.randint(minsize, maxsize)
y1 &#61; y0 &#43; random.randint(minsize, maxsize)
color &#61; (&#39;red&#39;, &#39;orange&#39;, &#39;yellow&#39;, &#39;green&#39;, &#39;blue&#39;)[random.randint(0, 4)]
self.canvas.create_rectangle(x0, y0, x1, y1, fill&#61;color, activefill&#61;&#39;black&#39;)
self.show_image()
def scroll_y(self, *args, **kwargs):
&#39;&#39;&#39; Scroll canvas vertically and redraw the image &#39;&#39;&#39;
self.canvas.yview(*args, **kwargs) # scroll vertically
self.show_image() # redraw the image
def scroll_x(self, *args, **kwargs):
&#39;&#39;&#39; Scroll canvas horizontally and redraw the image &#39;&#39;&#39;
self.canvas.xview(*args, **kwargs) # scroll horizontally
self.show_image() # redraw the image
def move_from(self, event):
&#39;&#39;&#39; Remember previous coordinates for scrolling with the mouse &#39;&#39;&#39;
self.canvas.scan_mark(event.x, event.y)
def move_to(self, event):
&#39;&#39;&#39; Drag (move) canvas to the new position &#39;&#39;&#39;
self.canvas.scan_dragto(event.x, event.y, gain&#61;1)
self.show_image() # redraw the image
def wheel(self, event):
&#39;&#39;&#39; Zoom with mouse wheel &#39;&#39;&#39;
x &#61; self.canvas.canvasx(event.x)
y &#61; self.canvas.canvasy(event.y)
bbox &#61; self.canvas.bbox(self.container) # get image area
if bbox[0] else: return # zoom only inside image area
scale &#61; 1.0
# Respond to Linux (event.num) or Windows (event.delta) wheel event
if event.num &#61;&#61; 5 or event.delta &#61;&#61; -120: # scroll down
i &#61; min(self.width, self.height)
if int(i * self.imscale) <30: return # image is less than 30 pixels
self.imscale /&#61; self.delta
scale /&#61; self.delta
if event.num &#61;&#61; 4 or event.delta &#61;&#61; 120: # scroll up
i &#61; min(self.canvas.winfo_width(), self.canvas.winfo_height())
if i self.imscale *&#61; self.delta
scale *&#61; self.delta
self.canvas.scale(&#39;all&#39;, x, y, scale, scale) # rescale all canvas objects
self.show_image()
def show_image(self, event&#61;None):
&#39;&#39;&#39; Show image on the Canvas &#39;&#39;&#39;
bbox1 &#61; self.canvas.bbox(self.container) # get image area
# Remove 1 pixel shift at the sides of the bbox1
bbox1 &#61; (bbox1[0] &#43; 1, bbox1[1] &#43; 1, bbox1[2] - 1, bbox1[3] - 1)
bbox2 &#61; (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 &#61; [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] &#61;&#61; bbox2[0] and bbox[2] &#61;&#61; bbox2[2]: # whole image in the visible area
bbox[0] &#61; bbox1[0]
bbox[2] &#61; bbox1[2]
if bbox[1] &#61;&#61; bbox2[1] and bbox[3] &#61;&#61; bbox2[3]: # whole image in the visible area
bbox[1] &#61; bbox1[1]
bbox[3] &#61; bbox1[3]
self.canvas.configure(scrollregion&#61;bbox) # set scroll region
x1 &#61; max(bbox2[0] - bbox1[0], 0) # get coordinates (x1,y1,x2,y2) of the image tile
y1 &#61; max(bbox2[1] - bbox1[1], 0)
x2 &#61; min(bbox2[2], bbox1[2]) - bbox1[0]
y2 &#61; 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 &#61; min(int(x2 / self.imscale), self.width) # sometimes it is larger on 1 pixel...
y &#61; min(int(y2 / self.imscale), self.height) # ...and sometimes not
image &#61; self.image.crop((int(x1 / self.imscale), int(y1 / self.imscale), x, y))
imagetk &#61; ImageTk.PhotoImage(image.resize((int(x2 - x1), int(y2 - y1))))
imageid &#61; self.canvas.create_image(max(bbox2[0], bbox1[0]), max(bbox2[1], bbox1[1]),
anchor&#61;&#39;nw&#39;, image&#61;imagetk)
self.canvas.lower(imageid) # set image into background
self.canvas.imagetk &#61; imagetk # keep an extra reference to prevent garbage-collection
path &#61; &#39;doge.jpg&#39; # place path to your image here
root &#61; tk.Tk()
app &#61; Zoom_Advanced(root, path&#61;path)
root.mainloop()