Добавление увеличения или уменьшения масштаба с помощью виджета Tkinter Canvas? - PullRequest
11 голосов
/ 25 марта 2011

Как бы я добавил масштабирование к следующему сценарию, я бы хотел привязать его к колесу мыши.Если вы тестируете этот скрипт в 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()

Ответы [ 4 ]

13 голосов
/ 12 апреля 2011

Насколько мне известно, встроенный масштаб класса Tkinter Canvas не будет автоматически масштабировать изображения. Если вы не можете использовать собственный виджет, вы можете масштабировать необработанное изображение и заменять его на холсте при вызове функции масштабирования.

Фрагмент кода ниже можно объединить с вашим исходным классом. Это делает следующее:

  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)

Обновление Я провел небольшое тестирование для различных масштабов и обнаружил, что resize / create_image использует довольно много памяти. Я запустил тест, используя 540x375 JPEG на Mac Pro с 32 ГБ ОЗУ. Вот память, используемая для различных масштабных коэффициентов:

 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, который выполняет это кадрирование / оконное масштабирование.

Обновление 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()
7 голосов
/ 02 июня 2011

Просто для блага других, которые находят этот вопрос, я прилагаю свой окончательный тестовый код, который использует масштабирование изображения / увеличительного стекла. В основном это просто изменение того, что уже опубликовано на самплебии. Это также очень круто видеть:).

Как я уже говорил, если вы используете этот скрипт в 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()
2 голосов
/ 11 апреля 2011

Может быть хорошей идеей будет посмотреть на виджет TkZinc вместо простого холста для того, что вы делаете, он поддерживает масштабирование через OpenGL.

0 голосов
/ 07 января 2018
  1. Пример расширенного увеличения на основе плиток.Как в Google Maps.
  2. Пример упрощенного увеличения , основанный на изменении размера всего изображения.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...