Tkinter画布缩放+移动/平移 [英] Tkinter canvas zoom + move/pan

查看:823
本文介绍了Tkinter画布缩放+移动/平移的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Tkinter的画布小部件具有以下内置功能:




  • 移动/平移画布(例如,单击并拖动) )和 canvas.scan_mark canvas.scan_dragto ,请参见。


    在Windows 7 64位系统上测试和Python 3.6.2。


    不要忘记在脚本的末尾放置图像的路径。

     #-*-编码:utf-8-*-
    #高级缩放示例。就像在Google地图中一样。
    #它只会缩放一个图块,而不缩放整个图像。因此,缩放后的图块会占用
    #不变的内存,而不会在大缩放时使用巨大的调整大小的图像填充该图块。
    导入随机
    从tkinter导入tkinter as tk
    导入ttk
    从PIL导入Image,ImageTk

    class AutoScrollbar(ttk.Scrollbar):
    '''滚动条,在不需要时会自动隐藏。
    仅在使用网格几何管理器'''
    def set(self,lo,hi)时有效:如果float(lo)< = 0.0且float(hi)>
    = 1.0:
    self.grid_remove()
    其他:
    self.grid()
    ttk.Scrollbar.set(self,lo,hi)

    def pack(self,** kw):
    提高tk.TclError('不能在此小部件中使用pack')

    def place(self,** kw):
    提高tk.TclError('不能在此小部件中使用位置')

    class Zoom_Advanced(ttk.Frame):
    '''图像的高级缩放'''
    def __init __(self,mainframe,path):
    '''初始化主框架'''
    ttk.Frame .__ init __(self,master = mainframe)
    self.master.title( '使用鼠标滚轮缩放')
    #画布的垂直和水平滚动条
    vbar = AutoScrollbar(self.master,orient ='vertical')
    hbar = AutoScrollbar(self.master,orient = '水平')
    vbar.grid(row = 0,column = 1,sticky ='ns')
    hbar.grid(row = 1,column = 0,sticky ='we')
    #创建画布并放置图像在它上
    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()#等待创建画布
    vbar.configure(command = self.scroll_y)#将滚动条绑定到画布
    hbar.configure(command = self.scroll_x)
    #使画布可扩展
    self.master.rowconfigure(0,weight = 1)
    self.master.columnconfigure(0, weight = 1)
    #将事件绑定到画布
    self.canvas.bind('< Configure>',self.show_image)#调整画布大小
    self.canvas.bind(' < ButtonPress-1>',self.move_from)
    self.canvas.bind('< B1-Motion>',self.move_to)
    self.c anvas.bind('< MouseWheel>',self.wheel)#仅适用于Windows和MacOS,而不适用于Linux
    self.canvas.bind('< Button-5>',self.wheel)#仅适用于Linux,仅向下滚动
    self.canvas.bind('< Button-4>',self.wheel)#仅适用于Linux,向上滚动
    self.image = Image.open(path) #打开图像
    self.width,self.height = self.image.size
    self.imscale = 1.0#Canvaas图像的缩放比例
    self.delta = 1.3#缩放幅度
    #将图像放入容器矩形,并使用它为图像设置适当的坐标
    self.container = self.canvas.create_rectangle(0,0,self.width,self.height,width = 0)
    #为测试目的绘制一些可选的随机矩形
    minsize,maxsize,number = 5,20,10
    对于range(number)中的n:
    x0 = random.randint(0, self.width-maxsize)
    y0 = random.randint(0,self.height-maxsize)
    x1 = x0 + random.randint(最小尺寸,最大尺寸)
    y1 = y0 + random.randint(最小尺寸,最大尺寸)
    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):
    '''垂直滚动画布并重新绘制图像'''
    self.canvas.yview( * args,** kwargs)#垂直滚动
    self.show_image()#重绘图像

    def scroll_x(self,* args,** kwargs):
    '' '水平滚动画布并重绘图像'''
    self.canvas.xview(* args,** kwargs)#水平滚动
    self.show_image()#重绘图像

    def move_from(self,event):
    '''记住以前使用鼠标滚动的坐标'''
    self.canvas.scan_mark(event.x,event.y)

    def move_to(self,event):
    '''将画布移动(移动)到新位置'''
    self.canvas.scan_dragto(event.x,event.y,gain = 1)
    self.show_image()#重绘图像

    def wheel(self,event):
    '''用鼠标滚轮缩放'''
    x = self。 canvas.canvasx(event.x)
    y = self.canvas.canvasy(event.y)
    bbox = self.canvas.bbox(self.container)#如果bbox是获取图像区域
    [ 0]< x < bbox [2]和bbox [1]< & bbox [3]:通过#好!在图像
    内部:return#仅在图像区域
    缩放= 1.0
    #在以下情况下响应Linux(event.num)或Windows(event.delta)滚轮事件
    event.num == 5或event.delta == -120:#向下滚动
    i = min(self.width,self.height)
    如果int(i * self.imscale)< 30:返回#图像小于30像素
    self.imscale / = self.delta
    scale / = self.delta
    如果event.num == 4或event.delta == 120 :#向上滚动
    i = min(self.canvas.winfo_width(),self.canvas.winfo_height())
    如果我< self.imscale:返回#1个像素大于可见区域
    self.imscale * = self.delta
    scale * = self.delta
    self.canvas.scale('all', x,y,scale,scale)#重新缩放所有画布对象
    self.show_image()

    def show_image(self,event = None):
    '''显示图像于Canvas'''
    bbox1 = self.canvas.bbox(self.container)#获取图像区域
    #删除bbox1两侧的像素偏移
    bbox1 =(bbox1 [0 ] + 1,bbox1 [1] + 1,bbox1 [2]-1,bbox1 [3]-1)
    bbox2 =(self.canvas.canvasx(0),#获取画布的可见区域
    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]),#获取滚动区域框
    max(bbox1 [2],bbox2 [ 2]),max(bbox1 [3], bbox2 [3])]
    如果bbox [0] == bbox2 [0]和bbox [2] == bbox2 [2]:#可见区域中的整个图像
    bbox [0] = bbox1 [0]
    bbox [2] = bbox1 [2]
    如果bbox [1] == bbox2 [1]和bbox [3] == bbox2 [3]:#整个可见区域中的图像
    bbox [1] = bbox1 [1]
    bbox [3] = bbox1 [3]
    self.canvas.configure(scrollregion = bbox)#设置滚动区域
    x1 = max(bbox2 [0]-bbox1 [0],0)#获取图像图块
    y1 = max(bbox2 [1]-bbox1 [1],0)的坐标(x1,y1,x2,y2)
    x2 =最小值(bbox2 [2],bbox1 [2])-bbox1 [0]
    y2 =最小值(bbox2 [3],bbox1 [3])-bbox1 [1]
    如果int(x2-x1)> 0并且int(y2-y1)> 0:#显示图像是否在可见区域
    x = min(int(x2 / self.imscale),self.width)#有时它在1个像素上更大...
    y = min(int (y2 / self.imscale),self.height)#...有时不是
    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) #将图像设置为背景
    self.canvas.imagetk = imagetk#保留额外的引用以防止垃圾收集

    path ='doge.jpg'#在此处放置图像的路径
    root = tk.Tk()
    app = Zoom_Advanced(root,path = path)
    root.mainloop()

    编辑:


    我创建了ev zh更高级的变焦。存在图像金字塔。


    版本3.0在Windows 7 64位和Python 3.7上进行了测试。

     #-*-编码:utf-8-*-
    #先进的缩放功能,可处理从小到大到几GB的各种类型的图像
    进口数学
    进口警告
    进口tkinter as tk

    从tkinter进口ttk
    从PIL进口Image,ImageTk

    类AutoScrollbar (ttk.Scrollbar):
    。不需要时会隐藏的滚动条。仅适用于网格几何管理器
    def set(self,lo,hi):如果float(lo)< = 0.0且float(hi)> = 1.0:
    self.grid_remove()
    else:
    self.grid()
    ttk.Scrollbar.set(self,lo,hi)

    def pack(self,** kw):
    加薪tk.TclError('不能与小部件一起使用包'+ self .__ class __.__ name__)

    def place(self,** kw):
    提高tk.TclError('不能使用place加上小部件'+ self .__ class __.__ name__)

    class CanvasImage:
    显示和缩放图像。
    def __init __(自己,占位符,路径):
    初始化ImageFrame。
    self.imscale = 1.0#画布图像缩放比例,对于外部类是公开的。
    self .__ delta = 1.3#缩放幅度
    self .__ filter = Image.ANTIALIAS#可以是:NEAREST, BILINEAR,BICUBIC和ANTIALIAS
    self .__ previous_state = 0#键盘的先前状态
    self.path = path#图像的路径,应该对外部类是公用的
    #在占位符中创建ImageFrame小部件
    self .__ imframe = ttk.Frame(placeholder)#ImageFrame对象的占位符
    #画布的垂直和水平滚动条
    hbar = AutoScrollbar(self .__ imframe,orient ='horizo​​ntal')
    vbar = AutoScrollbar(self .__ imframe,orient ='vertical')
    hbar.grid(row = 1,column = 0,sticky ='we')
    vbar.grid(row = 0,column = 1,sticky ='ns')
    #创建画布并用滚动条绑定它。外部类的公共类
    self.canvas = tk.Canvas(self .__ imframe,highlightthickness = 0,
    xscrollcommand = hbar.set,yscrollcommand = vbar.set)
    self.canvas.grid( row = 0,column = 0,sticky ='nswe')
    self.canvas.update()#等待创建画布
    hbar.configure(command = self .__ scroll_x)#将滚动条绑定到canvas
    vbar.configure(command = self .__ scroll_y)
    #将事件绑定到Canvas
    self.canvas.bind('< Configure>',lambda事件:self .__ show_image() )#调整画布大小
    self.canvas.bind('< ButtonPress-1>',self .__ move_from)#记住画布位置
    self.canvas.bind('< B1-Motion>' ,self .__ move_to)#将画布移到新位置
    self.canvas.bind('< MouseWheel>',self .__ wheel)#适用于Windows和MacOS的缩放,但不适用于Linux
    self.canvas .bind('< Button-5>',self .__ wheel)#缩放林ux,向下滚动
    self.canvas.bind('< Button-4>',self .__ wheel)#为Linux缩放,向上滚动
    #在空闲模式下处理击键,因为程序变慢在功能较弱的计算机上运行
    #,同时发生太多按键事件
    self.canvas.bind('< Key>',lambda事件:self.canvas.after_idle(self .__ keystroke ,event))
    #决定此图像是否巨大
    self .__ huge = False#巨大或否
    self .__ huge_size = 14000#定义巨大图像
    self的大小.__ band_width = 1024#瓷砖区域的宽度
    Image.MAX_IMAGE_PIXELS = 1000000000#使用警告警告抑制大图像
    的DecompressionBombError.catch_warnings():#禁止DecompressionBombWarning
    warnings.simplefilter('ignore ')
    self .__ image = Image.open(self.path)#打开图像,但不加载它
    self.imwidth,self.imheight = self.__image.si ze#公开外部类
    ,如果self.imwidth * self.imheight> self .__ huge_size * self .__ huge_size和\
    self .__ image.tile [0] [0] =='raw':#仅原始图像可以平铺
    self .__ huge = True#图像是巨大的
    self .__ offset = self .__ image.tile [0] [2]#初始图块偏移量
    self .__ tile = [self .__ image.tile [0] [0],# 'raw'
    [0,0,self.imwidth,0],#切片范围(矩形)
    self .__ offset,
    self .__ image.tile [0] [3]] #解码器的参数列表
    self .__ min_side = min(self.imwidth,self.imheight)#获取较小的图像面
    #创建图像金字塔
    self .__ pyramid = [自我。更小()]如果self .__ huge else [Image.open(self.path)]
    #设置图像金字塔
    self .__ ratio = max(self.imwidth,self.imheight)/ self的比率系数.__ huge_size if self .__ huge else 1.0
    self .__ curr_img = 0#当前图像金字塔
    self .__ scale = self.imscale * self .__ ratio#图像金字塔比例
    self .__ reduction = 2#图像金字塔
    w的还原度,h =自我。__pyramid [-1] .size
    而w> 512且h> 512:#顶部金字塔图像的大小约为512像素
    w / = self .__ reduction#缩小度划分
    h / = self .__ reduction#缩小度划分
    self .__ pyramid.append( self .__ pyramid [-1] .resize((int(w),int(h)),self .__ filter))
    #将图像放入容器矩形并使用它为图像$ b $设置适当的坐标b self.container = self.canvas.create_rectangle((0,0,self.imwidth,self.imheight),width = 0)
    self .__ show_image()#在画布
    self上显示图像。 canvas.focus_set()#将焦点设置在画布上

    def small(self):
    按比例调整图像大小,并返回较小的图像。
    w1,h1 = float(self.imwidth),float(self.imheight)
    w2,h2 = float(self .__ huge_size),float(self .__ huge_size)
    Aspect_ratio1 = w1 / h1
    Aspect_ratio2 = w2 / h2#如果Aspect_ratio1 == Aspect_ratio2则等于1.0

    image = Image.new('RGB',(int(w2),int(h2)) )
    k = h2 / h1#压缩率
    w = int(w2)#带宽
    elif Aspect_ratio1> Aspect_ratio2:
    image = Image.new('RGB',(int(w2),int(w2 / aspect_ratio1)))
    k = h2 / w1#压缩率
    w = int(w2) #band length
    else:#Aspect_ratio1< Aspect_ration2
    image = Image.new('RGB',(int(h2 * Aspect_ratio1),int(h2)))
    k = h2 / h1#压缩率
    w = int(h2 * Aspect_ratio1 )#频带长度
    i,j,n = 0,1,圆(0.5 + self.imheight / self .__ band_width)
    而i< self.imheight:
    print('\rOpening image:{j} from {n}'。format(j = j,n = n),end ='')
    band = min(self .__ band_width,self.imheight-i)#瓦片频带
    self .__ tile [1] [3] = band#设置频带宽度
    self .__ tile [2] = self .__ offset + self .imwidth * i * 3#瓦片偏移量(每个像素3个字节)
    self .__ image.close()
    self .__ image = Image.open(self.path)#重新打开/重置图像
    self .__ image.size =(self.imwidth,band)#设置图块带的大小
    self .__ image.tile = [self .__ tile]#设置图块
    裁剪= self .__ image.crop (((0,0,self.imwidth,band))#裁剪图带
    image.paste(cropped.resize((w,int(band * k)+1),self .__ filter),(0, int(i * k)))
    i + = band
    j + = 1
    print('\r'+ 30 *''+'\r',end ='') #隐藏打印的字符串
    返回图像

    def redraw_figures(self ):
    。虚拟功能可在子类中重绘图形。
    pass

    def grid(self,** kw):
    将CanvasImage窗口小部件放在父窗口小部件上。
    self .__ imframe.grid(** kw)#将CanvasImage小部件放置在网格上
    self .__ imframe.grid(sticky ='nswe')#使框架容器变为粘性
    self .__ imframe。 rowconfigure(0,weight = 1)#使canvas可扩展
    self .__ imframe.columnconfigure(0,weight = 1)

    def pack(self,** kw):
    例外:不能将此包与该小部件一起使用。
    引发异常(‘不能与小部件’+ self一起使用pack。__class __.__ name__)

    def place(self,** kw):
    例外:不能将此地方与该小部件一起使用。
    引发异常('不能在控件中使用位置'+ self .__ class __.__ name__)

    #noinspection PyUnusedLocal
    def __scroll_x(self,* args,** kwargs):
    。水平滚动画布,然后重新绘制图像。
    self.canvas.xview(* args)#水平滚动
    self .__ show_image()#重绘图像

    #noinspection PyUnusedLocal
    def __scroll_y(self,* args,** kwargs):
    垂直滚动画布,然后重新绘制图像。
    self.canvas.yview(* args)#垂直滚动
    self .__ show_image()#重绘图像

    def __show_image(self):
    ;"在画布上显示图像。实现正确的图像缩放,几乎就像在Google Maps
    box_image = self.canvas.coords(self.container)#获取图像区域
    box_canvas =(self.canvas.canvasx(0),#获取画布的可见区域
    self.canvas .canvasy(0),
    self.canvas.canvasx(self.canvas.winfo_width()),
    self.canvas.canvasy(self.canvas.winfo_height()))
    box_img_int = tuple(map(int,box_image))#转换为整数,否则将无法正常运行
    #获取滚动区域框
    box_scroll = [min(box_img_int [0],box_canvas [0]),min( box_img_int [1],box_canvas [1]),
    max(box_img_int [2],box_canvas [2]),max(box_img_int [3],box_canvas [3])]
    #如果box_scroll [0] == box_canvas [0]和box_scroll [2] == box_canvas [2],则图像位于可见区域
    中:
    box_scroll [0] = box_img_int [0]
    box_scroll [2] = box_img_int [2]
    #图像的垂直部分是i n如果box_scroll [1] == box_canvas [1]和box_scroll [3] == box_canvas [3],则可见区域

    box_scroll [1] = box_img_int [1]
    box_scroll [ 3] = box_img_int [3]
    #将滚动区域转换为元组和整数
    self.canvas.configure(scrollregion = tuple(map(int,box_scroll)))#设置滚动区域
    x1 = max(box_canvas [0]-box_image [0],0)#获取图像图块
    的坐标(x1,y1,x2,y2)
    y1 = max(box_canvas [1]-box_image [1], 0)
    x2 = min(box_canvas [2],box_image [2])-box_image [0]
    y2 = min(box_canvas [3],box_image [3])-box_image [1] $ b如果int(x2-x1)> 0并且int(y2-y1)> 0:#显示图像是否在可见区域
    中,如果self .__ huge和self .__ curr_img< 0:#显示大图像
    h = int((y2-y1)/ self.imscale)#瓦片带的高度
    self .__ tile [1] [3] = h#设置瓦片带的高度
    self .__ tile [2] = self .__ offset + self.imwidth * int(y1 / self.imscale)* 3
    self .__ image.close()
    self .__ image =图片。 open(self.path)#重新打开/重置图像
    self .__ image.size =(self.imwidth,h)#设置图块的大小
    self .__ image.tile = [self .__ tile]
    image = self .__ image.crop((int(x1 / self.imscale),0,int(x2 / self.imscale),h))
    else:#显示普通图像
    图像= self .__ pyramid [max(0,self .__ curr_img)]。crop(#从金字塔
    中裁剪当前img(int(x1 / self .__ scale),int(y1 / self .__ scale),
    int(x2 / self .__ scale),int(y2 / self .__ scale)))

    imagetk = ImageTk.PhotoImage(image.resize((int(x2-x1),int(y2-y1)),self .__ filter))
    imageid = self.canvas.create_image(max(box_canvas [0], box_img_int [0]),
    max(box_canvas [1],box_img_int [1]),
    anchor ='nw',image = imagetk)
    self.canvas.lower(imageid)#将图像设置为背景
    self.canvas.imagetk = imagetk#保留额外的引用以防止垃圾收集

    def __move_from(self,event):
    ;记住先前使用鼠标滚动的坐标。
    self.canvas.scan_mark(event.x,event.y)

    def __move_to(self,event):
    将画布拖动(移动)到新位置。
    self.canvas.scan_dragto(event.x,event.y,gain = 1)
    self .__ show_image()#缩放并显示在画布上

    def在外面(self,x,y):
    。检查点(x,y)是否在图像区域内。
    bbox = self.canvas.coords(self.container)#如果bbox [0]< x < bbox [2]和bbox [1]< & bbox [3]:
    返回False#点(x,y)在图像区域
    其他:
    返回True#点(x,y)在图像区域$ b $外部b
    def __wheel(自身,事件):
    用鼠标滚轮进行缩放。
    x = self.canvas.canvasx(event.x)#获取事件在画布上的坐标
    y = self.canvas.canvasy(event.y)
    if self.outside(x, y):返回#仅在图像区域内缩放
    scale = 1.0
    #如果event.num == 5则响应Linux(event.num)或Windows(event.delta)滚轮事件
    或event.delta == -120:#向下滚动,如果round(self .__ min_side * self.imscale)<较小,则
    30:返回#图像小于30像素
    self.imscale / = self .__ delta
    scale / = self .__ delta
    如果event.num == 4或event.delta == 120 :#向上滚动,更大
    i = min(self.canvas.winfo_width(),self.canvas.winfo_height())>>如果我< 1
    self.imscale:返回#1个像素大于可见区域
    self.imscale * = self .__ delta
    标度* = self .__ delta
    #从金字塔$ b $中获取适当的图像bk = self.imscale * self .__ ratio#临时系数
    self .__ curr_img = min((-1)* int(math.log(k,self .__ reduction)),len(self .__ pyramid)-1)
    self .__ scale = k * math.pow(self .__ reduction,max(0,self .__ curr_img))

    self.canvas.scale('all',x,y,缩放,缩放)#重新缩放所有对象
    #重新绘制一些图形,然后在屏幕上显示图像
    self.redraw_figures()#子类
    self的方法。__show_image()

    def __击键(自己,事件):
    用键盘滚动。
    独立于键盘,CapsLock,< Ctrl> +<键>等语言。
    if event.state-self .__ previous_state == 4:#表示按下Control键
    通过#如果按下Control键则不执行
    else:
    self .__ previous_state = event.state#记住上一个按键状态
    #如果[68,39,102]中的event.keycode,向上,向下,向左,向右按键
    :#向右滚动:键'D','向右或数字键6
    self .__ scroll_x('scroll',1,'unit',event = event)
    elif event.keycode in [65,37,100]:#向左滚动:键 A,左或数字键4
    self .__ scroll_x('scroll',-1,'unit',event = event)
    elif event.keycode in [87, 38,104]:#向上滚动:键'W','Up'或'Numpad-8'
    self .__ scroll_y('scroll',-1,'unit',event = event)
    elif event.keycode in [83,40,98]:#向下滚动:键'S','Down'或'Numpad-2'
    self .__ scroll_y('scroll',1,'unit',甚至t = event)

    def作物(self,bbox):
    从图像中裁剪矩形,然后将其返回
    如果self .__ huge:#图像很大,并且不完全在RAM中
    band = bbox [3]-bbox [1]#瓦片带的宽度
    self .__ tile [1] [ 3] = band#设置瓦片高度
    self .__ tile [2] = self .__ offset + self.imwidth * bbox [1] * 3#设置频段
    self .__ image.close( )
    self .__ image = Image.open(self.path)#重新打开/重置图像
    self .__ image.size =(self.imwidth,band)#设置图块带
    的大小self .__ image.tile = [self .__ tile]
    返回self .__ image.crop((bbox [0],0,bbox [2],band))
    其他:#图像完全在RAM中
    return self .__ pyramid [0] .crop(bbox)

    def destroy(self):
    ImageFrame析构函数
    self .__ image.close()
    map(lambda i:i.close,self .__ pyramid)#关闭所有金字塔图像
    del self .__ pyramid [:]#删除金字塔列表
    del self .__ pyramid#删除金字塔变量
    self.canvas.destroy()
    self .__ imframe.destroy()

    class MainWindow(ttk.Frame):
    主窗口类
    def __init __(自身,大型机,路径):
    初始化主框架。
    ttk.Frame .__ init __(self,master = mainframe)
    self.master.title('Advanced Zoom v3.0')
    self.master.geometry('800x600')#size
    self.master.rowconfigure(0,weight = 1)#使CanvasImage小部件可扩展
    self.master.columnconfigure(0,weight = 1)
    canvas = CanvasImage( self.master,path)#创建部件
    canvas.grid(row = 0,column = 0)#显示部件

    filename ='./data/img_plg5.png'#放置路径到您的图片
    #filename ='d:/Data/yandex_z18_1-1.tif'#巨大的TIFF文件1.4 GB
    #filename ='d:/Data/The_Garden_of_Earthly_Delights_by_Bosch_High_Resolution.jpg'
    #filename ='d:/Data/The_Garden_of_Earthly_Delights_by_Bosch_High_Resolution.tif'
    #filename ='d:/Data/heic1502a.tif'
    #filename ='d:/Data/land_shallow_topo_east.tif'
    #filename ='d:/Data/X1D5_B0002594.3FR'
    app = MainWindow(tk.Tk(),path = filename)
    app.mainloop()

    这是使用高级缩放功能的GitHub应用程序,用于带有多边形的手动图像批注


    Tkinter's canvas widget has built-in features to:

    • move/pan the canvas (for example with Click + Drag) with canvas.scan_mark and canvas.scan_dragto, see this question

    • zoom the vector elements on the canvas with canvas.scale, but sadly, this doesn't work for bitmap images on the canvas

    Fortunately, this method allows zooming of images (by manually redrawing the zoomed portion of the image). But:

    1. As we are redrawing a particular portion of the canvas, move/pan feature won't work anymore...

    2. We absolutely need to render more than the currently displayed area, to allow move/pan. Let's say we have 1000x1000 bitmap on the canvas, and we want to zoom by a factor 50x... How to avoid having a 50.000 x 50.000 pixels bitmap in memory? (2.5 gigapixels in RAM is too big). We could think about rendering the viewport only, or a bit more than the current viewport to allow panning, but then what to do once panning leads to the edge of the rendered zone?

    How to have a move/pan + zoom feature on Tkinter canvas, that works for images?

    解决方案

    Advanced zoom example. Like in Google Maps.

    Example video (longer video here):

    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. For the simplified zoom example look here.

    Tested on Windows 7 64-bit and Python 3.6.2.

    Do not forget to place a path to your image at the end of the script.

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

    EDIT:

    I've created even more advanced zoom. There is "image pyramid" for smooth zooming of large images and even ability to open and zoom huge TIFF files up to several gigabytes.

    Version 3.0 is tested on Windows 7 64-bit and Python 3.7.

    # -*- coding: utf-8 -*-
    # Advanced zoom for images of various types from small to huge up to several GB
    import math
    import warnings
    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 for 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 the widget ' + self.__class__.__name__)
    
        def place(self, **kw):
            raise tk.TclError('Cannot use place with the widget ' + self.__class__.__name__)
    
    class CanvasImage:
        """ Display and zoom image """
        def __init__(self, placeholder, path):
            """ Initialize the ImageFrame """
            self.imscale = 1.0  # scale for the canvas image zoom, public for outer classes
            self.__delta = 1.3  # zoom magnitude
            self.__filter = Image.ANTIALIAS  # could be: NEAREST, BILINEAR, BICUBIC and ANTIALIAS
            self.__previous_state = 0  # previous state of the keyboard
            self.path = path  # path to the image, should be public for outer classes
            # Create ImageFrame in placeholder widget
            self.__imframe = ttk.Frame(placeholder)  # placeholder of the ImageFrame object
            # Vertical and horizontal scrollbars for canvas
            hbar = AutoScrollbar(self.__imframe, orient='horizontal')
            vbar = AutoScrollbar(self.__imframe, orient='vertical')
            hbar.grid(row=1, column=0, sticky='we')
            vbar.grid(row=0, column=1, sticky='ns')
            # Create canvas and bind it with scrollbars. Public for outer classes
            self.canvas = tk.Canvas(self.__imframe, 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
            hbar.configure(command=self.__scroll_x)  # bind scrollbars to the canvas
            vbar.configure(command=self.__scroll_y)
            # Bind events to the Canvas
            self.canvas.bind('<Configure>', lambda event: self.__show_image())  # canvas is resized
            self.canvas.bind('<ButtonPress-1>', self.__move_from)  # remember canvas position
            self.canvas.bind('<B1-Motion>',     self.__move_to)  # move canvas to the new position
            self.canvas.bind('<MouseWheel>', self.__wheel)  # zoom for Windows and MacOS, but not Linux
            self.canvas.bind('<Button-5>',   self.__wheel)  # zoom for Linux, wheel scroll down
            self.canvas.bind('<Button-4>',   self.__wheel)  # zoom for Linux, wheel scroll up
            # Handle keystrokes in idle mode, because program slows down on a weak computers,
            # when too many key stroke events in the same time
            self.canvas.bind('<Key>', lambda event: self.canvas.after_idle(self.__keystroke, event))
            # Decide if this image huge or not
            self.__huge = False  # huge or not
            self.__huge_size = 14000  # define size of the huge image
            self.__band_width = 1024  # width of the tile band
            Image.MAX_IMAGE_PIXELS = 1000000000  # suppress DecompressionBombError for the big image
            with warnings.catch_warnings():  # suppress DecompressionBombWarning
                warnings.simplefilter('ignore')
                self.__image = Image.open(self.path)  # open image, but down't load it
            self.imwidth, self.imheight = self.__image.size  # public for outer classes
            if self.imwidth * self.imheight > self.__huge_size * self.__huge_size and \
               self.__image.tile[0][0] == 'raw':  # only raw images could be tiled
                self.__huge = True  # image is huge
                self.__offset = self.__image.tile[0][2]  # initial tile offset
                self.__tile = [self.__image.tile[0][0],  # it have to be 'raw'
                               [0, 0, self.imwidth, 0],  # tile extent (a rectangle)
                               self.__offset,
                               self.__image.tile[0][3]]  # list of arguments to the decoder
            self.__min_side = min(self.imwidth, self.imheight)  # get the smaller image side
            # Create image pyramid
            self.__pyramid = [self.smaller()] if self.__huge else [Image.open(self.path)]
            # Set ratio coefficient for image pyramid
            self.__ratio = max(self.imwidth, self.imheight) / self.__huge_size if self.__huge else 1.0
            self.__curr_img = 0  # current image from the pyramid
            self.__scale = self.imscale * self.__ratio  # image pyramide scale
            self.__reduction = 2  # reduction degree of image pyramid
            w, h = self.__pyramid[-1].size
            while w > 512 and h > 512:  # top pyramid image is around 512 pixels in size
                w /= self.__reduction  # divide on reduction degree
                h /= self.__reduction  # divide on reduction degree
                self.__pyramid.append(self.__pyramid[-1].resize((int(w), int(h)), self.__filter))
            # Put image into container rectangle and use it to set proper coordinates to the image
            self.container = self.canvas.create_rectangle((0, 0, self.imwidth, self.imheight), width=0)
            self.__show_image()  # show image on the canvas
            self.canvas.focus_set()  # set focus on the canvas
    
        def smaller(self):
            """ Resize image proportionally and return smaller image """
            w1, h1 = float(self.imwidth), float(self.imheight)
            w2, h2 = float(self.__huge_size), float(self.__huge_size)
            aspect_ratio1 = w1 / h1
            aspect_ratio2 = w2 / h2  # it equals to 1.0
            if aspect_ratio1 == aspect_ratio2:
                image = Image.new('RGB', (int(w2), int(h2)))
                k = h2 / h1  # compression ratio
                w = int(w2)  # band length
            elif aspect_ratio1 > aspect_ratio2:
                image = Image.new('RGB', (int(w2), int(w2 / aspect_ratio1)))
                k = h2 / w1  # compression ratio
                w = int(w2)  # band length
            else:  # aspect_ratio1 < aspect_ration2
                image = Image.new('RGB', (int(h2 * aspect_ratio1), int(h2)))
                k = h2 / h1  # compression ratio
                w = int(h2 * aspect_ratio1)  # band length
            i, j, n = 0, 1, round(0.5 + self.imheight / self.__band_width)
            while i < self.imheight:
                print('\rOpening image: {j} from {n}'.format(j=j, n=n), end='')
                band = min(self.__band_width, self.imheight - i)  # width of the tile band
                self.__tile[1][3] = band  # set band width
                self.__tile[2] = self.__offset + self.imwidth * i * 3  # tile offset (3 bytes per pixel)
                self.__image.close()
                self.__image = Image.open(self.path)  # reopen / reset image
                self.__image.size = (self.imwidth, band)  # set size of the tile band
                self.__image.tile = [self.__tile]  # set tile
                cropped = self.__image.crop((0, 0, self.imwidth, band))  # crop tile band
                image.paste(cropped.resize((w, int(band * k)+1), self.__filter), (0, int(i * k)))
                i += band
                j += 1
            print('\r' + 30*' ' + '\r', end='')  # hide printed string
            return image
    
        def redraw_figures(self):
            """ Dummy function to redraw figures in the children classes """
            pass
    
        def grid(self, **kw):
            """ Put CanvasImage widget on the parent widget """
            self.__imframe.grid(**kw)  # place CanvasImage widget on the grid
            self.__imframe.grid(sticky='nswe')  # make frame container sticky
            self.__imframe.rowconfigure(0, weight=1)  # make canvas expandable
            self.__imframe.columnconfigure(0, weight=1)
    
        def pack(self, **kw):
            """ Exception: cannot use pack with this widget """
            raise Exception('Cannot use pack with the widget ' + self.__class__.__name__)
    
        def place(self, **kw):
            """ Exception: cannot use place with this widget """
            raise Exception('Cannot use place with the widget ' + self.__class__.__name__)
    
        # noinspection PyUnusedLocal
        def __scroll_x(self, *args, **kwargs):
            """ Scroll canvas horizontally and redraw the image """
            self.canvas.xview(*args)  # scroll horizontally
            self.__show_image()  # redraw the image
    
        # noinspection PyUnusedLocal
        def __scroll_y(self, *args, **kwargs):
            """ Scroll canvas vertically and redraw the image """
            self.canvas.yview(*args)  # scroll vertically
            self.__show_image()  # redraw the image
    
        def __show_image(self):
            """ Show image on the Canvas. Implements correct image zoom almost like in Google Maps """
            box_image = self.canvas.coords(self.container)  # get image area
            box_canvas = (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()))
            box_img_int = tuple(map(int, box_image))  # convert to integer or it will not work properly
            # Get scroll region box
            box_scroll = [min(box_img_int[0], box_canvas[0]), min(box_img_int[1], box_canvas[1]),
                          max(box_img_int[2], box_canvas[2]), max(box_img_int[3], box_canvas[3])]
            # Horizontal part of the image is in the visible area
            if  box_scroll[0] == box_canvas[0] and box_scroll[2] == box_canvas[2]:
                box_scroll[0]  = box_img_int[0]
                box_scroll[2]  = box_img_int[2]
            # Vertical part of the image is in the visible area
            if  box_scroll[1] == box_canvas[1] and box_scroll[3] == box_canvas[3]:
                box_scroll[1]  = box_img_int[1]
                box_scroll[3]  = box_img_int[3]
            # Convert scroll region to tuple and to integer
            self.canvas.configure(scrollregion=tuple(map(int, box_scroll)))  # set scroll region
            x1 = max(box_canvas[0] - box_image[0], 0)  # get coordinates (x1,y1,x2,y2) of the image tile
            y1 = max(box_canvas[1] - box_image[1], 0)
            x2 = min(box_canvas[2], box_image[2]) - box_image[0]
            y2 = min(box_canvas[3], box_image[3]) - box_image[1]
            if int(x2 - x1) > 0 and int(y2 - y1) > 0:  # show image if it in the visible area
                if self.__huge and self.__curr_img < 0:  # show huge image
                    h = int((y2 - y1) / self.imscale)  # height of the tile band
                    self.__tile[1][3] = h  # set the tile band height
                    self.__tile[2] = self.__offset + self.imwidth * int(y1 / self.imscale) * 3
                    self.__image.close()
                    self.__image = Image.open(self.path)  # reopen / reset image
                    self.__image.size = (self.imwidth, h)  # set size of the tile band
                    self.__image.tile = [self.__tile]
                    image = self.__image.crop((int(x1 / self.imscale), 0, int(x2 / self.imscale), h))
                else:  # show normal image
                    image = self.__pyramid[max(0, self.__curr_img)].crop(  # crop current img from pyramid
                                        (int(x1 / self.__scale), int(y1 / self.__scale),
                                         int(x2 / self.__scale), int(y2 / self.__scale)))
                #
                imagetk = ImageTk.PhotoImage(image.resize((int(x2 - x1), int(y2 - y1)), self.__filter))
                imageid = self.canvas.create_image(max(box_canvas[0], box_img_int[0]),
                                                   max(box_canvas[1], box_img_int[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
    
        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()  # zoom tile and show it on the canvas
    
        def outside(self, x, y):
            """ Checks if the point (x,y) is outside the image area """
            bbox = self.canvas.coords(self.container)  # get image area
            if bbox[0] < x < bbox[2] and bbox[1] < y < bbox[3]:
                return False  # point (x,y) is inside the image area
            else:
                return True  # point (x,y) is outside the image area
    
        def __wheel(self, event):
            """ Zoom with mouse wheel """
            x = self.canvas.canvasx(event.x)  # get coordinates of the event on the canvas
            y = self.canvas.canvasy(event.y)
            if self.outside(x, y): 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, smaller
                if round(self.__min_side * 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, bigger
                i = min(self.canvas.winfo_width(), self.canvas.winfo_height()) >> 1
                if i < self.imscale: return  # 1 pixel is bigger than the visible area
                self.imscale *= self.__delta
                scale        *= self.__delta
            # Take appropriate image from the pyramid
            k = self.imscale * self.__ratio  # temporary coefficient
            self.__curr_img = min((-1) * int(math.log(k, self.__reduction)), len(self.__pyramid) - 1)
            self.__scale = k * math.pow(self.__reduction, max(0, self.__curr_img))
            #
            self.canvas.scale('all', x, y, scale, scale)  # rescale all objects
            # Redraw some figures before showing image on the screen
            self.redraw_figures()  # method for child classes
            self.__show_image()
    
        def __keystroke(self, event):
            """ Scrolling with the keyboard.
                Independent from the language of the keyboard, CapsLock, <Ctrl>+<key>, etc. """
            if event.state - self.__previous_state == 4:  # means that the Control key is pressed
                pass  # do nothing if Control key is pressed
            else:
                self.__previous_state = event.state  # remember the last keystroke state
                # Up, Down, Left, Right keystrokes
                if event.keycode in [68, 39, 102]:  # scroll right: keys 'D', 'Right' or 'Numpad-6'
                    self.__scroll_x('scroll',  1, 'unit', event=event)
                elif event.keycode in [65, 37, 100]:  # scroll left: keys 'A', 'Left' or 'Numpad-4'
                    self.__scroll_x('scroll', -1, 'unit', event=event)
                elif event.keycode in [87, 38, 104]:  # scroll up: keys 'W', 'Up' or 'Numpad-8'
                    self.__scroll_y('scroll', -1, 'unit', event=event)
                elif event.keycode in [83, 40, 98]:  # scroll down: keys 'S', 'Down' or 'Numpad-2'
                    self.__scroll_y('scroll',  1, 'unit', event=event)
    
        def crop(self, bbox):
            """ Crop rectangle from the image and return it """
            if self.__huge:  # image is huge and not totally in RAM
                band = bbox[3] - bbox[1]  # width of the tile band
                self.__tile[1][3] = band  # set the tile height
                self.__tile[2] = self.__offset + self.imwidth * bbox[1] * 3  # set offset of the band
                self.__image.close()
                self.__image = Image.open(self.path)  # reopen / reset image
                self.__image.size = (self.imwidth, band)  # set size of the tile band
                self.__image.tile = [self.__tile]
                return self.__image.crop((bbox[0], 0, bbox[2], band))
            else:  # image is totally in RAM
                return self.__pyramid[0].crop(bbox)
    
        def destroy(self):
            """ ImageFrame destructor """
            self.__image.close()
            map(lambda i: i.close, self.__pyramid)  # close all pyramid images
            del self.__pyramid[:]  # delete pyramid list
            del self.__pyramid  # delete pyramid variable
            self.canvas.destroy()
            self.__imframe.destroy()
    
    class MainWindow(ttk.Frame):
        """ Main window class """
        def __init__(self, mainframe, path):
            """ Initialize the main Frame """
            ttk.Frame.__init__(self, master=mainframe)
            self.master.title('Advanced Zoom v3.0')
            self.master.geometry('800x600')  # size of the main window
            self.master.rowconfigure(0, weight=1)  # make the CanvasImage widget expandable
            self.master.columnconfigure(0, weight=1)
            canvas = CanvasImage(self.master, path)  # create widget
            canvas.grid(row=0, column=0)  # show widget
    
    filename = './data/img_plg5.png'  # place path to your image here
    #filename = 'd:/Data/yandex_z18_1-1.tif'  # huge TIFF file 1.4 GB
    #filename = 'd:/Data/The_Garden_of_Earthly_Delights_by_Bosch_High_Resolution.jpg'
    #filename = 'd:/Data/The_Garden_of_Earthly_Delights_by_Bosch_High_Resolution.tif'
    #filename = 'd:/Data/heic1502a.tif'
    #filename = 'd:/Data/land_shallow_topo_east.tif'
    #filename = 'd:/Data/X1D5_B0002594.3FR'
    app = MainWindow(tk.Tk(), path=filename)
    app.mainloop()
    

    P.S. Here is the GitHub application using advanced zoom for manual image annotation with polygons.

    这篇关于Tkinter画布缩放+移动/平移的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆