使用 Tkinter Canvas 小部件添加放大和缩小? [英] Adding Zooming in and out with a Tkinter Canvas Widget?

查看:30
本文介绍了使用 Tkinter Canvas 小部件添加放大和缩小?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

How would I add zooming in and out to the following script, i'd like to bind it to the mousewheel. If you're testing this script on linux don't forget to change the MouseWheel event to Button-4 and 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()

推荐答案

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

To my knowledge the built-in Tkinter Canvas class scale will not auto-scale images. If you are unable to use a custom widget, you can scale the raw image and replace it on the canvas when the scale function is invoked.

下面的代码片段可以合并到您的原始类中.它执行以下操作:

The code snippet below can be merged into your original class. It does the following:

  1. 缓存 Image.open() 的结果.
  2. 添加一个 redraw() 函数来计算缩放后的图像并将其添加到画布中,如果有的话,还会删除之前绘制的图像.
  3. 使用鼠标坐标作为图像放置的一部分.我只是将 x 和 y 传递给 create_image 函数,以显示图像位置如何随着鼠标移动而移动.您可以将其替换为您自己的中心/偏移计算.
  4. 这使用 Linux 鼠标滚轮按钮 4 和 5(您需要将其推广到 Windows 等上).
  1. Caches the result of Image.open().
  2. Adds a redraw() function to calculate the scaled image and adds that to the canvas, and also removes the previously-drawn image if any.
  3. Uses the mouse coordinates as part of the image placement. I just pass x and y to the create_image function to show how the image placement shifts around as the mouse moves. You can replace this with your own center/offset calculation.
  4. This uses the Linux mousewheel buttons 4 and 5 (you'll need to generalize it to work on Windows, etc).

(更新)代码:

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)

更新 我对不同的比例进行了一些测试,发现 resize/create_image 使用了相当多的内存.我在具有 32GB RAM 的 Mac Pro 上使用 540x375 JPEG 运行测试.这是用于不同比例因子的内存:

Update I did a bit of testing for varying scales and found that quite a bit of memory is being used by resize / create_image. I ran the test using a 540x375 JPEG on a Mac Pro with 32GB RAM. Here is the memory used for different scale factors:

 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

鉴于上述情况,更有效的解决方案可能是确定将显示图像的视口的大小,计算围绕鼠标坐标中心的裁剪矩形,使用矩形裁剪图像,然后仅缩放裁剪部分.这应该使用常量内存来存储临时图像.否则,您可能需要使用第三方 Tkinter 控件来为您执行此裁剪/窗口缩放.

Given the above, a more efficient solution might be to determine the size of the viewport where the image will be displayed, calculate a cropping rectangle around the center of the mouse coordinates, crop the image using the rect, then scale just the cropped portion. This should use constant memory for storing the temporary image. Otherwise you may need to use a 3rd party Tkinter control which performs this cropping / windowed scaling for you.

更新 2 有效但过于简化的裁剪逻辑,只是为了让您开始:

Update 2 Working but oversimplified cropping logic, just to get you started:

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

这篇关于使用 Tkinter Canvas 小部件添加放大和缩小?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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