您的位置:

Python GUI 开发实例: 用 tkinter 创建图像编辑器

一、什么是图像编辑器

图像编辑器是一种用于修改和增强图像的软件工具,在数字化时代中,图像编辑器越来越重要。

通过使用图像编辑器软件,我们可以对图像进行剪切、旋转、色彩调整、滤镜添加、文字添加等操作,让我们更好的表达和传递信息。

二、为什么使用 Tkinter

在 Python 中,有很多 GUI 工具包可供使用,如 Qt、wxPython 等,但是,Tkinter 是 Python 默认内置的,因此使用 Tkinter 不需要安装任何额外的库。

Tkinter 是一个功能强大、简单易用、跨平台的 GUI 工具包,它支持不同平台的标准 GUI 工具。

虽然 Tkinter 本身可能显得有些陈旧,但是它非常灵活,可以应用于许多应用程序的开发。

三、图像编辑器开发思路

我们想要开发的图像编辑器需要具备以下几个基本功能:

  • 打开、保存图片
  • 缩放、旋转图片
  • 添加文字
  • 添加滤镜和边框

四、打开、保存图片

使用 Tkinter 自带的组件,我们可以轻松创建一个文件选择对话框,使用 Pillow 库读取和保存图片,如下:

from tkinter import filedialog
from PIL import Image, ImageTk

def open_image():
  file_path = filedialog.askopenfilename()
  image = Image.open(file_path)
  photo = ImageTk.PhotoImage(image)
  canvas.create_image(0, 0, image=photo, anchor='nw')
  canvas.image = photo
  
def save_image():
  file_path = filedialog.asksaveasfilename(defaultextension='.jpg')
  image = Image.frombytes('RGB', canvas.size(), canvas.postscript(colormode='color'))
  image.save(file_path)

五、缩放、旋转图片

我们可以使用 Tkinter 中的 Canvas 组件来显示图片,并使用鼠标滚动事件实现缩放功能,使用鼠标拖动事件实现旋转功能。

from math import cos, sin

def scale(delta):
  x, y = canvas.canvasx(event.x), canvas.canvasy(event.y)
  s = delta/120
  if image_id:
    canvas.scale(image_id, x, y, s, s)

def rotate(event):
  global angle
  x, y = canvas.canvasx(event.x), canvas.canvasy(event.y)
  if image_id:
    angle += 5
    angle %= 360
    rad = angle/180 * pi
    c, s = cos(rad), sin(rad)
    canvas.coords(image_id, *rotate_points(x, y, c, s))

def rotate_points(x, y, c, s):
  xr = x-c*x+s*y
  yr = y-s*x+c*y
  return xr-50, yr-50, xr+50, yr-50, xr+50, yr+50, xr-50, yr+50

六、添加文字

我们可以使用 Tkinter 自带的 Text 组件和 Canvas 组件来添加文字,使用鼠标点击事件添加文字,并可以使用右键菜单修改文字字体和大小。

from tkinter import font, Menu

class Textbox:
  def __init__(self, x, y):
    self.text = tk.Text(canvas, height=1, font='TkDefaultFont 12', bd=0, highlightthickness=0)
    self.text.insert('0.0', 'Text')
    self.text.focus_set()
    self.text.bind('', self.submit)
    self.id = canvas.create_window(x, y, window=self.text, anchor='nw')
    
  def submit(self, event):
    text = self.text.get('1.0', 'end-1c')
    canvas.delete(self.id)
    self.draw_text(event, text)
    
  def draw_text(self, event, text):
    x, y = canvas.canvasx(event.x), canvas.canvasy(event.y)
    font_name = font.nametofont('TkDefaultFont').actual()['family']
    font_size = font.nametofont('TkDefaultFont').actual()['size']
    canvas.create_text(x, y, text=text, font=(font_name, font_size), tags='text')
    
def font_setting():
  tag = canvas.gettags(canvas.find_withtag('current'))[0]
  if tag == 'text':
    font_size = font.nametofont('TkDefaultFont').actual()['size']
    new_size = simpledialog.askinteger('大小', '输入新字号', initialvalue=font_size)
    font.nametofont('TkDefaultFont').configure(size=new_size)

  

七、添加滤镜和边框

使用 Pillow 库中的 ImageFilter 可以添加图像滤镜,使用 ImageOps 可以添加图像边框。

from PIL import ImageFilter, ImageOps

def add_filter():
  global image
  image = image.filter(ImageFilter.CONTOUR)
  photo = ImageTk.PhotoImage(image)
  canvas.create_image(0, 0, image=photo, anchor='nw')
  canvas.image = photo
  
def add_border():
  global image
  image = ImageOps.expand(image, border=20, fill='white')
  photo = ImageTk.PhotoImage(image)
  canvas.create_image(0, 0, image=photo, anchor='nw')
  canvas.image = photo

八、完整代码示例

下面是一个完整的图像编辑器代码示例:

from tkinter import *
from tkinter import filedialog, simpledialog, font
from PIL import Image, ImageTk, ImageFilter, ImageOps
from math import cos, sin, pi

root = Tk()
root.title('Image Editor')

canvas = Canvas(root, bg='white')
canvas.pack(fill=BOTH, expand=True)

image = None
angle = 0
image_id = None

def open_image():
  global image, image_id
  file_path = filedialog.askopenfilename()
  image = Image.open(file_path)
  photo = ImageTk.PhotoImage(image)
  canvas.create_image(0, 0, image=photo, anchor='nw')
  canvas.image = photo
  image_id = canvas.find_withtag('image')[0]

def save_image():
  file_path = filedialog.asksaveasfilename(defaultextension='.jpg')
  image = Image.frombytes('RGB', canvas.size(), canvas.postscript(colormode='color'))
  image.save(file_path)

def scale(delta):
  x, y = canvas.canvasx(event.x), canvas.canvasy(event.y)
  s = delta/120
  if image_id:
    canvas.scale(image_id, x, y, s, s)

def rotate(event):
  global angle
  x, y = canvas.canvasx(event.x), canvas.canvasy(event.y)
  if image_id:
    angle += 5
    angle %= 360
    rad = angle/180 * pi
    c, s = cos(rad), sin(rad)
    canvas.coords(image_id, *rotate_points(x, y, c, s))

def rotate_points(x, y, c, s):
  xr = x-c*x+s*y
  yr = y-s*x+c*y
  return xr-50, yr-50, xr+50, yr-50, xr+50, yr+50, xr-50, yr+50
  
def add_filter():
  global image
  image = image.filter(ImageFilter.CONTOUR)
  photo = ImageTk.PhotoImage(image)
  canvas.create_image(0, 0, image=photo, anchor='nw')
  canvas.image = photo
  
def add_border():
  global image
  image = ImageOps.expand(image, border=20, fill='white')
  photo = ImageTk.PhotoImage(image)
  canvas.create_image(0, 0, image=photo, anchor='nw')
  canvas.image = photo

class Textbox:
  def __init__(self, x, y):
    self.text = Text(canvas, height=1, font='TkDefaultFont 12', bd=0, highlightthickness=0)
    self.text.insert('0.0', 'Text')
    self.text.focus_set()
    self.text.bind('', self.submit)
    self.id = canvas.create_window(x, y, window=self.text, anchor='nw')
    
  def submit(self, event):
    text = self.text.get('1.0', 'end-1c')
    canvas.delete(self.id)
    self.draw_text(event, text)
    
  def draw_text(self, event, text):
    x, y = canvas.canvasx(event.x), canvas.canvasy(event.y)
    font_name = font.nametofont('TkDefaultFont').actual()['family']
    font_size = font.nametofont('TkDefaultFont').actual()['size']
    canvas.create_text(x, y, text=text, font=(font_name, font_size), tags='text')

def font_setting():
  tag = canvas.gettags(canvas.find_withtag('current'))[0]
  if tag == 'text':
    font_size = font.nametofont('TkDefaultFont').actual()['size']
    new_size = simpledialog.askinteger('大小', '输入新字号', initialvalue=font_size)
    font.nametofont('TkDefaultFont').configure(size=new_size)

menu = Menu(root)
root.config(menu=menu)

file_menu = Menu(menu, tearoff=0)
menu.add_cascade(label="文件", menu=file_menu)
file_menu.add_command(label="打开", command=open_image)
file_menu.add_command(label="保存", command=save_image)

edit_menu = Menu(menu, tearoff=0)
menu.add_cascade(label="编辑", menu=edit_menu)
edit_menu.add_command(label="滤镜", command=add_filter)
edit_menu.add_command(label="添加边框", command=add_border)

text_menu = Menu(menu, tearoff=0)
menu.add_cascade(label="文本", menu=text_menu)
text_menu.add_command(label="添加文字", command=lambda: Textbox(event.x, event.y))
text_menu.add_command(label="修改字体", command=font_setting)

canvas.bind("
   ", rotate)
canvas.bind("
    ", lambda event : scale(-120))
canvas.bind("
     ", lambda event : scale(120))

root.mainloop()