在移动的Tkinter画布上裁剪图像的可见部分

时间:2019-05-23 16:56:05

标签: python tkinter python-imaging-library crop tkinter-canvas

我正在为我的图像处理程序创建图像预览。因此,我正在使用 tkinter canvas 来显示,缩放和移动图像。为了节省内存,我想使用 pillow 裁剪图像并调整其大小,以便仅显示可见部分。

Like this

应该剪掉红色的剥离部分

为了移动图像,我使用了this question的技术,并裁剪了图像,尝试从this question修改代码。

所以我最终得到了这段代码(试图尽可能简化)

裁剪部分位于 redraw 函数中:

import tkinter as tk
import tkinter.ttk as ttk
from PIL import Image, ImageTk

class ImagePreview(ttk.Frame):
    def __init__(self, master=None):
        ttk.Frame.__init__(self, master)
        self.master.geometry('800x500+10+10')
        self.master.columnconfigure(0, weight=1)
        self.master.rowconfigure(0, weight=1)
        self.grid(column=0, row=0, sticky=tk.N+tk.E+tk.S+tk.W)
        self.columnconfigure(0, weight=1)
        self.rowconfigure(0, weight=1)
        self.master.update()

        self.canvasScale     = 1.0 # Multiplier to scale the image
        self.sourceImage     = Image.open('./test.jpg') # Must be taller than wide, for now
        self.previewImage    = ImageTk.PhotoImage(self.sourceImage) # Cropped Image
        self.previewImage_ID = None # Canvas item ID of the cropped Image

        self.init_widgets()
        self.master.update_idletasks()
        self.set_binds()

        self.adjust_canvas_size()

    def init_widgets(self):
        self.previewCanvas = tk.Canvas(self)
        self.previewCanvas.config(background='yellow', highlightthickness=0, width=self.winfo_width(), height=self.winfo_height())
        self.previewCanvas.grid(column=0, row=0)

    def set_binds(self):
        self.bind('<Configure>', self.adjust_canvas_size)   # Makes the canvas always the same size as the main frame

        self.previewCanvas.bind("<ButtonPress-1>",  self.shift_start)
        self.previewCanvas.bind("<B1-Motion>",      self.shift_move)
        self.previewCanvas.bind('<MouseWheel>',     self.zoom)

    def adjust_canvas_size(self, event=None):
        self.previewCanvas.config(width=self.winfo_width()-5, height=self.winfo_height()-5)
        self.canvasScale = self.previewCanvas.winfo_height() / self.sourceImage.height
        self.redraw()

    def shift_start(self, event):
        self.previewCanvas.scan_mark(event.x, event.y)

    def shift_move(self, event):
        self.previewCanvas.scan_dragto(event.x, event.y, gain=1)
        self.redraw()

    def zoom(self, event):
        if event.delta >= 0 and self.canvasScale < 5:
            self.canvasScale *= 1.2
        if event.delta <  0 and self.canvasScale > 0.1:
            self.canvasScale *= 1/1.2
        self.redraw()

    def redraw(self):
        if self.previewImage_ID:
            self.previewCanvas.delete(self.previewImage_ID)
            self.previewCanvas.delete(self.orientationLine)

        imageWidth, imageHeight = self.sourceImage.size

        # Crop size to make image and canvas scale 1:1
        cropWidth, cropHeight = int(imageWidth*self.canvasScale), int(imageHeight*self.canvasScale)
        size = cropWidth, cropHeight
        tempImage = self.sourceImage.resize(size, resample=Image.LANCZOS)

        if self.sourceImage.height > self.sourceImage.width: # Didn't came up with an idea yet to crop wide and tall pictures with the same algorithm

            # The distance between left (or right) edge of the canvas
            # and the left (or right) edge of the original image position
            z = int((self.previewCanvas.winfo_width()-cropWidth)/2)+1 

            # To simplify it, I'm just cutting the left edge,
            # when the image hits the left edge of the canvas
            if self.previewCanvas.canvasx(0) > z:
                leftEdge = self.previewCanvas.canvasx(0) - z
                print('Cut:',leftEdge)
            else:
                leftEdge = 0

            rightEdge = tempImage.width
            topEdge = 0
            bottomEdge = tempImage.height

        #crop
        tempImage = tempImage.crop((leftEdge, topEdge, rightEdge, bottomEdge))

        # To check that there is no overhang, on the not visible part of the canvas,
        # what is sadly the problem
        tempImage.save('./croppedImage.jpg') 

        # draw
        x = self.previewCanvas.winfo_width()  / 2
        y = self.previewCanvas.winfo_height() / 2
        self.previewImage    = ImageTk.PhotoImage(tempImage)
        self.previewImage_ID = self.previewCanvas.create_image(x, y, image=self.previewImage)
        self.previewCanvas.scale(tk.ALL, x, y,self.canvasScale, self.canvasScale)
        self.orientationLine = self.previewCanvas.create_line(0,0,self.previewCanvas.winfo_width(),self.previewCanvas.winfo_height())

def main():
    root = tk.Tk()
    application = ImagePreview(root)
    application.mainloop()

if __name__ == '__main__':
    main()

问题是,理论上图像被正确裁剪(调整大小的图像与重叠的不可见部分之间的差异是正确的)。但是,如果保存裁切后的图像,则可以清楚地看到边界之外仍然有一部分未被裁切。 最糟糕的是,这种重叠越来越多,图像移出边界的次数也就越多。

我很确定我在缩放方面缺少一些东西,但是过去几天我找不到错误。

0 个答案:

没有答案