【问题标题】:Adding Zooming in and out with a Tkinter Canvas Widget?使用 Tkinter Canvas 小部件添加放大和缩小?
【发布时间】:2011-07-23 03:35:39
【问题描述】:

如何将放大和缩小添加到以下脚本,我想将其绑定到鼠标滚轮。如果您在 linux 上测试此脚本,请不要忘记将 MouseWheel 事件更改为 Button-4 和 Button-5。

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

【问题讨论】:

  • 您实际上是要缩放画布还是仅缩放图像?
  • 画布上的一切,图像以及各种线条和圆圈最终都会出现在画布上。一旦放置,所有东西都保持其 x,y 坐标非常重要。

标签: python tkinter zooming


【解决方案1】:

查看TkZinc 小部件而不是简单的画布可能是一个好主意,因为它支持通过 OpenGL 进行缩放。

【讨论】:

  • 我确实研究过 TkZinc,但如果可能的话,我想只使用 Python 2.7.1 附带的标准模块来开发 GUI
【解决方案2】:

据我所知,内置的 Tkinter Canvas 类比例不会自动缩放图像。如果您无法使用自定义小部件,您可以缩放原始图像并在调用缩放功能时将其替换到画布上。

下面的代码 sn-p 可以合并到你原来的类中。它执行以下操作:

  1. 缓存Image.open()的结果。
  2. 添加一个 redraw() 函数来计算缩放后的图像并将其添加到画布中,如果有的话,还会删除之前绘制的图像。
  3. 使用鼠标坐标作为图像放置的一部分。我只是将x and y 传递给create_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

鉴于上述情况,更有效的解决方案可能是确定将显示图像的视口大小,计算围绕鼠标坐标中心的裁剪矩形,使用矩形裁剪图像,然后仅缩放裁剪的部分。这应该使用常量内存来存储临时图像。否则,您可能需要使用第 3 方 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()

【讨论】:

  • 我不能放大过去 2 否则我得到:运行时错误!程序:C:\Python27\pythonw.exe 此应用程序以异常方式请求运行时终止它,请联系应用程序的支持团队以获取更多信息。
  • @Symon 不客气,很高兴它能帮助你取得进步。鉴于画布无法自动调整图像大小,redraw() 方法手动执行此操作 - 重新缩放并将图像重新添加到画布。要同时缩放画布上绘制的任何矢量(线、椭圆、多边形),您可以在上面的 redraw() 方法中添加对 self.canvas.scale(ALL, x, y, self.scale, self.scale) 的调用。
  • @Symon 嗯,我使用的是 Linux,还没有看到那个错误。你放大的图像真的很大吗?
  • 这是一个 2058x2165 211KB .jpg 图片,我看看能不能在互联网上找到任何东西。缩放画布上的项目也非常感谢!
  • @Symon 我用一个简单的裁剪/缩放示例更新了代码,该示例非常快并且使用恒定内存。希望这很有用。祝您工作顺利!
【解决方案3】:

只是为了其他发现此问题的人的利益,我附上了我的最终测试代码,该代码使用画中画/放大镜缩放。它基本上只是对已经发布的 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()

【讨论】:

  • 设法在 2020 年在 Python 3.8 上启动了这个脚本。只更改了前两行:import tkinter(注意大小写)和 from PIL import Image、ImageTk。谢谢你的工作。
【解决方案4】:
  1. Advanced zoom 示例,基于图块。就像在 Google 地图中一样。
  2. Simplified zoom 示例,基于调整整个图像的大小。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-29
    • 2013-05-24
    • 1970-01-01
    • 2019-06-04
    • 1970-01-01
    • 2015-06-04
    相关资源
    最近更新 更多