在日常的生活和工作中, 我们经常需要对照片进行涂鸦或者备注重要信息,就像在纸上绘画一样。除了通过手机涂鸦照片,我们也可以通过电脑在指定图像上“画画”或“写字”。
Python作为一门功能强大且易于学习的编程语言,拥有许多强大的库来处理图像。本节教程将介绍如何通过Python的Tkinter模块和Python的第三方图像处理Pillow库制作一个简单的图像编辑器和查看器,它完全可以执行基本的图像操作,包括应用滤镜、调整图像亮度和对比度、旋转图像、翻转图像、剪切图像以及在指定图像上用铅笔绘制图形。
1、图片查看编辑器介绍
我们的目标是使用Python的Tkinter模块构建一个简易的图片查看和编辑应用程序,支持查看指定文件夹内的所有图片文件,支持前后翻页查看图片,并提供了图片编辑功能包括在图片上绘制图形、对图片进行旋转或翻转等基础操作,以及各种特效操作包括灰度、彩色反转、高斯模糊、锐化等,还可以在指定图片上裁剪部分图像。
编辑图像方法:点击“打开图片”按钮后导入后缀名为jpg、png、gif、bmp的图像,导入后可在画布上根据原始图片大小呈现图片,再点击“编辑图片”按钮后就可以对已导入的图像进行绘图。程序通过监听鼠标左键的按下(开始绘图)和抬起(停止绘图)完成整个绘图过程。
剪切图像方法:在右侧的工具栏中,点击“剪切图片”工具按钮。此时,在图片上拖动鼠标产生虚线裁剪框,调整裁剪框的大小和位置,选择想要裁剪的部分图片区域,最后按“保存剪切”按钮保存到指定文件夹内。
图片格式:支持查看和编辑常见的图片格式(JPG、PNG、BMP、GIF)。
保存图像方法:点击“保存”按钮即可保存已绘制的图像,可以保存为指定后缀名的图像文件,包括jpg、png、gif图像。
2、图片查看编辑器界面演示
3、代码实现
(1)安装PIL库
首先,确保你已经安装了imagehash库和PIL库(Pillow)。如果还没有安装,可以通过pip安装:
pip install Pillow
pip install imagehash
(2)图片查看编辑器,完整代码如下所示:
动手练一练:
import os, webbrowser, imagehash
import tkinter as tk
from tkinter import colorchooser, ttk, messagebox, filedialog
from PIL import ImageTk, Image, ImageGrab, ImageEnhance, ImageFilter, ImageOps
# 定义函数,用于创建新图像
def create_image(width, height, color=(32, 99, 252)):
# 创建新图像
image = Image.new("RGB", (width, height), color)
return image
# 创建一个40x40的绿色图像,用于显示启动画面
loader_image = create_image(40, 40)
# 定义ImageEditor类,用于封装程序的功能
class ImageEditor(tk.Tk):
# 初始化程序数据
def __init__(self):
super().__init__()
# 参数设为1,隐藏窗口边框和任务栏图标,只显示启动画面
self.wm_overrideredirect(1)
self.title("图片查看编辑器")
self.loading_label = None
self.pencil_color = '#FF0000' # 默认铅笔颜色为红色
# 创建启动页面框架
self.splash_frame = tk.Frame(self, background='#7bdfff', highlightbackground="#055aba", highlightthickness=2)
self.splash_frame.pack(fill="both", expand=True)
self.image_editor_window = tk.Frame(self)
self.loading = loader_image.resize((39, 39), Image.Resampling.LANCZOS)
self.loading = ImageTk.PhotoImage(self.loading)
self.delay = 50
self.title_label = tk.Label(self, text='图片查看编辑器', foreground="#032f93",
font=12, background='#7bdfff')
self.title_label.place(x=180, y=65)
self.x = 8
# 始化参数
self.button_background = 'grey'
self.original_image_resized = None
self.image_copy_resized = None
self.modified_img_resized = None
self.image_before_draw = None
self.draw_state = False
self.cut_state = False
self.mirrored = False
self.vertical = False
self.current_index = None
self.image_paths = []
self.lines_drawn = []
self.current_image_size = None
self.current_resized_image_size = None
self.original_image = None
self.modified_img = None
self.image_copy = None
self.rect = None
self.event_x = self.event_y = None
self.rectangles = []
self.point_x = self.point_y = None
self.image_x_co = self.image_y_co = None
self.original_hash = None
self.max_height = 812
self.max_width = 1220
self.degree = 90
self.error = None
# 在窗口上创建一个Canvas组件,用于绘制图形
self.canvas = tk.Canvas(self.image_editor_window, bd=0, highlightbackground="#BCBCBC", background="#BCBCBC")
self.canvas.bind('<B1-Motion>', self.draw_image)
self.canvas.bind('<ButtonPress-1>', self.mouse_position_get)
self.canvas.bind('<ButtonRelease-1>', self.button_release)
self.canvas.pack(fill="both", expand=True)
self.button_frame_color = "#e6e6e6"
# 创建左下角图片信息栏,用于显示图片位置和大小
self.info_frame = tk.Frame(self.image_editor_window, background="#BCBCBC")
self.info_frame.pack(fill="x")
self.state_var = tk.StringVar()
ttk.Label(self.info_frame,
textvariable=self.state_var,
font=('宋体', 12),
background="#BCBCBC",
foreground="black").pack(side=tk.LEFT, padx=5, pady=5)
self.state_var.set("请选择一张图片进行处理")
# 创建底部框架
self.bottom_frame = tk.Frame(self.image_editor_window)
self.bottom_frame.pack(fill="x")
self.button_frame = tk.Frame(self.bottom_frame)
self.button_frame.pack()
# 选择上一张图片
previous_button = tk.Button(self.button_frame, text="< 上一个", background=self.button_frame_color,
command=self.previous_image,
padx=5, bd=0, cursor="hand2",
font=('宋体', 12), foreground="black", borderwidth=2)
previous_button.bind("<Enter>", lambda e: mouse_hover(previous_button, color='#aceafc'))
previous_button.bind("<Leave>", lambda e: mouse_not_hover(previous_button, color=self.button_frame_color))
previous_button.pack(side="left", padx=2, pady=8)
# 逆时针旋转按键布置
rotate_n = tk.Button(self.button_frame, text="逆时针旋转", background=self.button_frame_color,
command=lambda: self.image_rotate(0), padx=5, bd=0, cursor="hand2",
font=('宋体', 12), foreground="black", borderwidth=2)
rotate_n.bind("<Enter>", lambda e: mouse_hover(rotate_n, color='#aceafc'))
rotate_n.bind("<Leave>", lambda e: mouse_not_hover(rotate_n, color=self.button_frame_color))
rotate_n.pack(side="left", padx=6, pady=8)
# 顺时针旋转按键布置
rotate_s = tk.Button(self.button_frame, text="顺时针旋转", background=self.button_frame_color,
command=lambda: self.image_rotate(1), padx=5, bd=0, cursor="hand2",
font=('宋体', 12), foreground="black", borderwidth=2)
rotate_s.bind("<Enter>", lambda e: mouse_hover(rotate_s, color='#aceafc'))
rotate_s.bind("<Leave>", lambda e: mouse_not_hover(rotate_s, color=self.button_frame_color))
rotate_s.pack(side="left", padx=6, pady=8)
# 水平翻转按键布置
level_flip = tk.Button(self.button_frame, text="水平翻转", background=self.button_frame_color,
command=self.mirror, padx=5, bd=0, cursor="hand2",
font=('宋体', 12), foreground="black", borderwidth=2)
level_flip.bind("<Enter>", lambda e: mouse_hover(level_flip, color='#aceafc'))
level_flip.bind("<Leave>", lambda e: mouse_not_hover(level_flip, color=self.button_frame_color))
level_flip.pack(side="left", padx=6, pady=8)
# 垂直翻转按键布置
vertical_flip = tk.Button(self.button_frame, text="垂直翻转", background=self.button_frame_color,
command=self.vertical_image, padx=5, bd=0, cursor="hand2",
font=('宋体', 12), foreground="black", borderwidth=2)
vertical_flip.bind("<Enter>", lambda e: mouse_hover(vertical_flip, color='#aceafc'))
vertical_flip.bind("<Leave>", lambda e: mouse_not_hover(vertical_flip, color=self.button_frame_color))
vertical_flip.pack(side="left", padx=6, pady=8)
# 选择下一张图片
next_button = tk.Button(self.button_frame, text="下一个 >", command=self.next_image,
background=self.button_frame_color, padx=5, bd=0, cursor="hand2",
font=('宋体', 12), foreground="black", borderwidth=2)
next_button.bind("<Enter>", lambda e: mouse_hover(next_button, color='#aceafc'))
next_button.bind("<Leave>", lambda e: mouse_not_hover(next_button, color=self.button_frame_color))
next_button.pack(side="left", padx=2, pady=8)
# 退出按钮
window_close = tk.Button(self.image_editor_window, compound="left", width=10,
text="退出", font=('宋体', 12), foreground="black", bd=0, background="#feacb6", borderwidth=4,
command=self.window_close, cursor="hand2")
window_close.bind("<Enter>", lambda e: mouse_hover(window_close, color='#e6e6e6'))
window_close.bind("<Leave>", lambda e: mouse_not_hover(window_close, color="#feacb6"))
window_close.place(x=1418, y=720)
# 创建右边工具栏框架
self.side_frame = tk.Frame(self.image_editor_window, background="#BCBCBC")
adjust_button = tk.Button(self.side_frame, text="调整", cursor="hand2",
width=15, padx=4, pady=4, font=('宋体', 13),
command=self.open_adjustment_window)
adjust_button.bind("<Enter>", lambda e: mouse_hover(adjust_button, color='#aceafc'))
adjust_button.bind("<Leave>", lambda e: mouse_not_hover(adjust_button, color="#e6e6e6"))
adjust_button.pack(padx=5, pady=5)
filter_button = tk.Button(self.side_frame, text="过滤", cursor="hand2",
width=15, padx=4, pady=4, font=('宋体', 13),
command=self.open_filter_window)
filter_button.bind("<Enter>", lambda e: mouse_hover(filter_button, color='#aceafc'))
filter_button.bind("<Leave>", lambda e: mouse_not_hover(filter_button, color="#e6e6e6"))
filter_button.pack(padx=5, pady=5)
self.reset_button = tk.Button(self.image_editor_window, text="还原",
width=15, padx=4, pady=4, font=('宋体', 13),
command=self.reset, cursor="hand2")
self.reset_button.bind("<Enter>", lambda e: mouse_hover(self.reset_button, color='#aceafc'))
self.reset_button.bind("<Leave>", lambda e: mouse_not_hover(self.reset_button, color="#e6e6e6"))
self.cut_var = tk.BooleanVar() # 创建一个BooleanVar变量来存储复选框的状态
self.cut_button = tk.Checkbutton(self.image_editor_window, text="剪切图片", variable=self.cut_var, padx=4,
pady=4,command=self.start_cut, cursor="hand2", font=('宋体', 13), width=8)
self.cut_button.bind("<Enter>", lambda e: mouse_hover(self.cut_button, color='#aceafc'))
self.cut_button.bind("<Leave>", lambda e: mouse_not_hover(self.cut_button, color="#e6e6e6"))
self.warn_label = tk.Label(self.image_editor_window, text="请按住鼠标左键拖动鼠标,选择想要剪切的部分", background="#BCBCBC", font=('宋体', 13))
self.check_var = tk.BooleanVar() # 创建一个BooleanVar变量来存储复选框的状态
self.draw_button = tk.Checkbutton(self.image_editor_window, text="编辑图片", variable=self.check_var, padx=4,
pady=4,command=self.start_draw, cursor="hand2", font=('宋体', 13), width=8)
self.draw_button.bind("<Enter>", lambda e: mouse_hover(self.draw_button, color='#aceafc'))
self.draw_button.bind("<Leave>", lambda e: mouse_not_hover(self.draw_button, color="#e6e6e6"))
# 绘制图片区域
self.editor_frame = ttk.LabelFrame(self.image_editor_window)
self.pencil_frame = ttk.LabelFrame(self.editor_frame)
self.pencil_frame.pack(side=tk.TOP)
self.pencil_size_label = tk.Label(self.pencil_frame, text="铅笔尺寸:", width=10)
self.pencil_size_label.pack(side=tk.LEFT, padx=5, pady=5)
self.choose_pencil_size = tk.Scale(self.pencil_frame, from_=1, to=20, orient=tk.HORIZONTAL)
self.choose_pencil_size.pack(side=tk.LEFT, padx=5)
# 选择铅笔颜色
self.fore_color_frame = ttk.LabelFrame(self.editor_frame)
self.fore_color_frame.pack(side=tk.TOP)
fore_color_chooser = tk.Button(self.fore_color_frame,
text="铅笔颜色",
command = self.fore_color_change,
width=10,
)
fore_color_chooser.pack(side=tk.LEFT, padx=5, pady=5)
self.input_fore_color = tk.StringVar()
self.input_fore_color.set("#FF0000")
change_fore_color = self.register(self.change_fore_color)
self.enter_fore_color = tk.Entry(self.fore_color_frame,
textvariable=self.input_fore_color,
width=8,
validate='key', # 当输入框被编辑时启用验证
validatecommand=(change_fore_color, '%P'),
)
self.enter_fore_color.pack(side=tk.LEFT, padx=5)
self.show_fore_color = tk.Frame(self.fore_color_frame)
self.show_fore_color.pack(expand=tk.YES, fill=tk.BOTH, side=tk.LEFT, padx=5)
self.fore_color_set= tk.Button(self.show_fore_color, text="", bg="#FF0000", command=self.fore_color_change, width=5)
self.fore_color_set.pack(fill=tk.BOTH, expand=True)
self.draw_save = tk.Button(self.editor_frame, compound='left', text="保存编辑", width=10,
foreground="black", font=('宋体', 12), background="#a8cdfe",
command=self.image_after_draw, cursor="hand2", borderwidth=4)
self.draw_save.pack(side=tk.LEFT, padx=10, pady=10)
self.draw_save.bind("<Enter>", lambda e: mouse_hover(self.draw_save, color='#e6e6e6'))
self.draw_save.bind("<Leave>", lambda e: mouse_not_hover(self.draw_save, color='#a8cdfe'))
self.draw_cancel = tk.Button(self.editor_frame, compound='left', text="取消编辑", width=10,
foreground="black", font=('宋体', 12), background="#feacb6",
command=self.image_draw_cancel, cursor="hand2", borderwidth=4)
self.draw_cancel.pack(side=tk.LEFT, padx=10, pady=10)
self.draw_cancel.bind("<Enter>", lambda e: mouse_hover(self.draw_cancel, color='#e6e6e6'))
self.draw_cancel.bind("<Leave>", lambda e: mouse_not_hover(self.draw_cancel, color='#feacb6'))
# 剪切图片时弹出按钮
self.crop_save = tk.Button(self.image_editor_window, compound='left', text="保存剪切", width=10,
foreground="black", font=('宋体', 12), background="#a8cdfe",
command=self.cut_image, cursor="hand2", borderwidth=4)
self.crop_save.bind("<Enter>", lambda e: mouse_hover(self.crop_save, color='#e6e6e6'))
self.crop_save.bind("<Leave>", lambda e: mouse_not_hover(self.crop_save, color='#a8cdfe'))
self.crop_cancel = tk.Button(self.image_editor_window, compound='left', text="取消保存", width=10,
foreground="black", font=('宋体', 12), background="#feacb6",
command=self.cut_image_cancel, cursor="hand2", borderwidth=4)
self.crop_cancel.bind("<Enter>", lambda e: mouse_hover(self.crop_cancel, color='#e6e6e6'))
self.crop_cancel.bind("<Leave>", lambda e: mouse_not_hover(self.crop_cancel, color='#feacb6'))
# 创建右下角保存按钮
self.save_button = tk.Button(self.image_editor_window, text="保存", compound="left", width=10,
foreground="black", font=('宋体', 12), background="#a8cdfe",
command=self.save, cursor="hand2", borderwidth=4)
self.save_button.bind("<Enter>", lambda e: mouse_hover(self.save_button, color='#e6e6e6'))
self.save_button.bind("<Leave>", lambda e: mouse_not_hover(self.save_button, color='#a8cdfe'))
# 左下角显示版本信息
version_label = tk.Label(self.bottom_frame,text="版本V2.10", background=self.button_frame_color,
foreground="black", font=('宋体', 12))
version_label.place(x=20, y=10)
# 右下角统计图片数量
self.status_bar = tk.Label(self.bottom_frame, background=self.button_frame_color,
foreground="black", font=('宋体', 12))
self.status_bar.place(x=1200, y=10)
# 打开图片按钮
self.open_image_button = tk.Button(self.image_editor_window, command=self.open_image, cursor="hand2",
compound='top', text="请点击打开图片",
font=('宋体', 20), foreground="black", bd=0, background="#e6e6e6", borderwidth=4)
self.open_image_button.bind("<Enter>", lambda e: mouse_hover(self.open_image_button, color='#cfcfcf'))
self.open_image_button.bind("<Leave>", lambda e: mouse_not_hover(self.open_image_button, color='#e6e6e6'))
self.open_image_button.place(x=685, y=350)
self.error_b = tk.Button(self.image_editor_window, text="不支持这种文件格式。", font=('宋体', 11), padx=8, pady=8)
# 将窗口关闭事件与window_close函数关联
self.protocol("WM_DELETE_WINDOW", self.window_close)
self.show_splash_window()
self.mainloop()
# 定义函数,用于改变铅笔颜色
def change_fore_color(self, color):
try:
self.fore_color_set['bg'] = color
except:
pass
return True
# 选择指定的颜色
def fore_color_change(self):
try:
# 使用askcolor显示颜色选择器,并设置初始颜色
color = colorchooser.askcolor(title="请选择填充颜色", initialcolor=self.enter_fore_color.get())
if color[1] != None:
self.input_fore_color.set(color[1])
self.pencil_color = color[1]
self.canvas.configure(cursor='arrow')
except:
# 如果颜色值输入错误,则弹出提示框
messagebox.showerror("错误", f"颜色输入值错误“{self.enter_fore_color.get()}”")
color = colorchooser.askcolor(title="请选择填充颜色", initialcolor="#000000")
if color[1] != None:
self.input_fore_color.set(color[1])
self.pencil_color = color[1]
self.canvas.configure(cursor='arrow')
# 创建启动动画
def show_splash_window(self):
# 设置窗口居中
window_width = 500
window_height = 100
screen_width = self.winfo_screenwidth()
screen_height = self.winfo_screenheight()
x = (screen_width - window_width) / 2
y = (screen_height - window_height) / 2
self.geometry('%dx%d+%d+%d' % (window_width, window_height, x, y))
self.loading_label = tk.Label(self.splash_frame, image=self.loading, bd=0)
self.loading_label.place(x=self.x, y=15)
self.x += 42
if self.x != 470:
self.after(self.delay, self.show_splash_window)
else:
self.title_label.destroy()
self.splash_frame.destroy()
self.loading_label.destroy()
self.wm_overrideredirect(0)
self.image_editor_window.pack(fill="both", expand=True)
self.state('zoomed') # 最大化窗口
# 打开图片
def open_image(self):
# 创建顶部菜单栏
self.menu = tk.Menu(self, bg='#33b3fd')
self.config(menu=self.menu)
filemenu = tk.Menu(self.menu, tearoff=0)
self.menu.add_cascade(label='文件', menu=filemenu)
filemenu.add_command(label='打开', command=self.open_image)
filemenu.add_command(label='另存为', command=self.save)
help_menu = tk.Menu(self.menu, tearoff=0)
help_menu.add_command(label="关于程序", command=self.about_image_editor)
help_menu.add_command(label="程序讲解", command=self.image_editor_help)
help_menu.add_separator()
help_menu.add_command(label="退出", command=self.window_close)
self.menu.add_cascade(label="帮助", menu=help_menu)
# 选择打开图片的类型
file_types = [
("所有图像文件", ".png .gif .jpg .jpeg .bmp"),
('jpg文件', '*.jpg'),
('png文件', '*.png'),
('gif文件', '*.gif'),
('bmp文件', '*.bmp'),
('jpeg文件', '*.jpeg')
]
file_object = filedialog.askopenfile(filetype=file_types)
if file_object:
self.open_image_button.configure(text="打开图片", compound="left", width=10,
font=('宋体', 12), foreground="black",
bd=0, background="#b5fec1", borderwidth=4)
self.open_image_button.bind("<Enter>", lambda e: mouse_hover(self.open_image_button, color='#e6e6e6'))
self.open_image_button.bind("<Leave>", lambda e: mouse_not_hover(self.open_image_button, color='#b5fec1'))
self.open_image_button.place(x=1418, y=680)
self.side_frame.place(x=1380, y=90)
self.save_button.place(x=1418, y=640)
self.draw_button.place(x=1460, y=232)
self.cut_button.place(x=1340, y=232)
self.reset_button.place(x=1385, y=190)
self.save_button.configure(state="normal")
filename = file_object.name
directory = filename.replace(os.path.basename(filename), "")
files_list = os.listdir(directory)
self.image_paths = []
for file in files_list:
if '.jpg' in file or \
'.png' in file or \
'.gif' in file or \
'.bmp' in file or \
'.jpeg' in file or \
'.GIF' in file or \
'.BMP' in file or \
'.JPG' in file or \
'.JPEG' in file or \
'.PNG' in file:
self.image_paths.append(os.path.join(directory, file))
for i, image in enumerate(self.image_paths):
if image == filename:
self.current_index = i
self.show_image(image=self.image_paths[self.current_index])
def resize_image(self, image):
image.thumbnail((self.max_width, self.max_height))
self.original_hash = imagehash.average_hash(image)
self.current_resized_image_size = (image.size[0], image.size[1])
return image
# 定义打开图片方法
def create_image_object(self, image):
# 打开图像
self.original_image = Image.open(image)
# 检查图像模式,如果是透明背景,则转换为白色背景
if self.original_image.mode == 'RGBA':
# 转换图像模式为RGB,并添加白色背景
background = Image.new('RGB', self.original_image.size, (255, 255, 255))
background.paste(self.original_image, mask=self.original_image.split()[-1]) # 使用透明度通道作为蒙版
self.original_image = background
self.current_image_size = self.original_image.size
self.modified_img = self.original_image
self.image_copy = self.original_image
# 打开图像
self.original_image_resized = Image.open(image)
# 检查图像模式,如果是透明背景,则转换为白色背景
if self.original_image_resized.mode == 'RGBA':
# 转换图像模式为RGB,并添加白色背景
background = Image.new('RGB', self.original_image_resized.size, (255, 255, 255))
background.paste(self.original_image_resized, mask=self.original_image_resized.split()[-1]) # 使用透明度通道作为蒙版
self.original_image_resized = background
self.modified_img_resized = self.original_image_resized
self.image_copy_resized = self.original_image_resized
im = self.resize_image(self.original_image_resized)
im = ImageTk.PhotoImage(im)
return im
# 显示图片
def show_image(self, image=None, modified=None):
self.status_bar.configure(text=f"图片 : {self.current_index + 1} of {len(self.image_paths)}")
if self.error:
self.error_b.place_forget()
self.error = None
im = None
if image:
try:
im = self.create_image_object(image)
except:
self.image_copy = self.modified_img = self.original_image_resized = self.original_image = None
self.canvas.image = ''
self.error_b.place(x=600, y=380)
self.error = True
return
elif modified:
im = modified
image_width, image_height = self.current_resized_image_size[0], self.current_resized_image_size[1]
self.image_x_co, self.image_y_co = (self.winfo_screenwidth() / 2) - image_width / 2, (
self.max_height / 2) - image_height / 2
self.canvas.image = im
if image_height < self.max_height:
self.canvas.create_image(self.image_x_co, self.image_y_co, image=im, anchor="nw")
else:
self.canvas.create_image(self.image_x_co, 0, image=im, anchor="nw")
self.state_var.set(f'图片信息 | {self.image_paths[self.current_index]}【尺寸:{image_width}x{image_height}】')
# 选择上一张图片函数
def previous_image(self):
if self.image_paths:
if self.current_index != 0:
self.current_index -= 1
self.show_image(image=self.image_paths[self.current_index])
# 选择下一张图片函数
def next_image(self):
if self.image_paths:
if self.current_index != len(self.image_paths) - 1:
self.current_index += 1
self.show_image(image=self.image_paths[self.current_index])
# 图像旋转函数
def image_rotate(self, direction):
if self.original_image and not self.error:
angle = 90
# direction=1为顺时针,否则逆时针
if direction:
angle = -90
self.modified_img_resized = self.image_copy_resized.rotate(angle, expand=True)
self.image_copy_resized = self.modified_img_resized
self.modified_img = self.image_copy.rotate(angle, expand=True)
self.image_copy = self.modified_img
self.image_copy_resized = self.modified_img_resized
self.current_resized_image_size = self.modified_img_resized.size
im = ImageTk.PhotoImage(self.modified_img_resized)
self.show_image(modified=im)
# 水平翻转函数
def mirror(self):
if self.original_image and not self.error:
if not self.mirrored:
self.modified_img = self.image_copy.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
self.modified_img_resized = self.image_copy_resized.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
self.mirrored = True
else:
self.modified_img = self.modified_img.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
self.modified_img_resized = self.modified_img_resized.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
self.mirrored = False
self.image_copy = self.modified_img
self.image_copy_resized = self.modified_img_resized
im = ImageTk.PhotoImage(self.modified_img_resized)
self.show_image(modified=im)
# 垂直翻转
def vertical_image(self):
if self.original_image and not self.error:
if not self.vertical:
self.modified_img = self.image_copy.transpose(Image.Transpose.FLIP_TOP_BOTTOM)
self.modified_img_resized = self.image_copy_resized.transpose(Image.Transpose.FLIP_TOP_BOTTOM)
self.vertical = True
else:
self.modified_img = self.modified_img.transpose(Image.Transpose.FLIP_TOP_BOTTOM)
self.modified_img_resized = self.modified_img_resized.transpose(Image.Transpose.FLIP_TOP_BOTTOM)
self.vertical = False
self.image_copy = self.modified_img
self.image_copy_resized = self.modified_img_resized
im = ImageTk.PhotoImage(self.modified_img_resized)
self.show_image(modified=im)
# 获取鼠标位置
def mouse_position_get(self, event):
if not self.draw_state and self.cut_state:
if self.rect:
self.rectangles = []
self.canvas.delete(self.rect)
self.rect = self.canvas.create_rectangle(0, 0, 0, 0, outline="black", width=2)
self.point_x, self.point_y = event.x, event.y
# 释放鼠标按钮
def button_release(self, event):
if self.cut_state:
self.crop_save.place(x=1290, y=280)
self.crop_cancel.place(x=1400, y=280)
self.warn_label.place_forget()
# 开始绘制图片
def start_draw(self):
if self.original_image and not self.error:
if self.check_var.get() and not self.draw_state:
# 创建一个新的白色背景图片,尺寸与原图相同
self.image_before_draw = Image.new('RGB', size=self.current_image_size)
self.image_before_draw.paste(self.image_copy, (0, 0))
self.canvas.configure(cursor='pencil')
self.editor_frame.place(x=1330, y=280)
self.button_frame.pack_forget()
self.side_frame.place_forget()
self.cut_button.place_forget()
self.save_button.configure(state="disable")
self.draw_state = True
else:
if self.lines_drawn:
for line in self.lines_drawn:
self.canvas.delete(line)
self.lines_drawn = []
self.image_before_draw = None
self.image_before_draw = self.image_copy
self.canvas.configure(cursor='arrow')
self.draw_state = False
self.button_frame.pack()
self.side_frame.place(x=1380, y=90)
self.save_button.place(x=1418, y=640)
self.cut_button.place(x=1340, y=232)
self.open_image_button.place(x=1418, y=680)
self.editor_frame.place_forget()
# 开始剪切图片
def start_cut(self):
if self.original_image and not self.error:
# 确保绘图状态和裁剪状态两者不能同时处于活动状态
if self.draw_state:
self.start_draw()
if self.cut_var.get() and not self.cut_state:
self.canvas.configure(cursor='plus')
self.cut_state = True
self.reset_button.place_forget()
self.button_frame.pack_forget()
self.side_frame.place_forget()
self.draw_button.place_forget()
self.warn_label.place(x=1200, y=280)
else:
self.canvas.configure(cursor='arrow')
self.crop_save.place_forget()
self.crop_cancel.place_forget()
self.warn_label.place_forget()
if self.rect:
self.canvas.delete(self.rect)
self.cut_state = False
self.side_frame.place(x=1380, y=90)
self.save_button.place(x=1418, y=640)
self.draw_button.place(x=1460, y=232)
self.cut_button.place(x=1340, y=232)
self.reset_button.place(x=1385, y=190)
self.button_frame.pack()
# 定义绘图和剪切方法
def draw_image(self, event):
if self.cut_state:
if not self.rectangles:
self.rectangles.append(self.rect)
image_width, image_height = self.current_resized_image_size[0], self.current_resized_image_size[1]
x_co_1, x_co_2 = int((self.winfo_screenwidth() / 2) - image_width / 2), int(
(self.winfo_screenwidth() / 2) + image_width / 2)
y_co_1, y_co_2 = int(self.max_height / 2 - image_height / 2), int((self.max_height / 2) + image_height / 2)
if x_co_2 > event.x > x_co_1 and y_co_1 + 2 < event.y < y_co_2:
self.canvas.coords(self.rect, self.point_x, self.point_y, event.x, event.y)
self.canvas.itemconfig(self.rect, dash=(10, 10)) # 设置虚线样式
self.event_x, self.event_y = event.x, event.y
elif self.draw_state:
image_width, image_height = self.current_resized_image_size[0], self.current_resized_image_size[1]
x_co_1, x_co_2 = int((self.winfo_screenwidth() / 2) - image_width / 2), int(
(self.winfo_screenwidth() / 2) + image_width / 2)
y_co_1, y_co_2 = int(self.max_height / 2 - image_height / 2), int((self.max_height / 2) + image_height / 2)
if x_co_2 > self.point_x > x_co_1 and y_co_1 < self.point_y < y_co_2:
if x_co_2 > event.x > x_co_1 and y_co_1 < event.y < y_co_2:
self.pencil_size = self.choose_pencil_size.get()
lines = self.canvas.create_line(self.point_x, self.point_y, event.x, event.y,
fill=self.pencil_color, width=self.pencil_size,
capstyle=tk.ROUND, smooth=tk.TRUE, splinesteps=36)
x_co_1, y_co_1, x_co_2, y_co2 = ((self.point_x - self.image_x_co) * self.current_image_size[0])/self.current_resized_image_size[0], ((self.point_y - self.image_y_co)*self.current_image_size[1])/self.current_resized_image_size[1], ((event.x - self.image_x_co)*self.current_image_size[0])/self.current_resized_image_size[0], ((event.y - self.image_y_co)*self.current_image_size[1])/self.current_resized_image_size[1]
self.lines_drawn.append(lines)
self.point_x, self.point_y = event.x, event.y
# 保存已剪切的图像
def cut_image(self):
if self.rectangles:
x_co_1, y_co_1, x_co_2, y_co2 = ((self.point_x - self.image_x_co) * self.current_image_size[0])/self.current_resized_image_size[0], ((self.point_y - self.image_y_co) * self.current_image_size[1]) / self.current_resized_image_size[1], ((self.event_x - self.image_x_co) * self.current_image_size[0]) / self.current_resized_image_size[0], ((self.event_y - self.image_y_co) * self.current_image_size[1]) / self.current_resized_image_size[1]
self.image_copy = self.image_copy.crop((int(x_co_1), int(y_co_1), int(x_co_2), int(y_co2)))
x_co_1, y_co_1, x_co_2, y_co2 = self.point_x - self.image_x_co, self.point_y - self.image_y_co, self.event_x - self.image_x_co, self.event_y - self.image_y_co
self.save()
self.cut_var.set(False) # 确保初始状态
else:
messagebox.showinfo(title="无法剪切!", message="请按住鼠标左键拖动鼠标,选择想要剪切的部分")
# 关闭绘图功能
def image_draw_cancel(self):
self.check_var.set(False) # 确保初始状态
self.draw_state = False
if self.lines_drawn:
for line in self.lines_drawn:
self.canvas.delete(line)
self.lines_drawn = []
self.image_before_draw = None
self.image_before_draw = self.image_copy
self.canvas.configure(cursor='arrow')
self.crop_save.place_forget()
self.crop_cancel.place_forget()
self.side_frame.place(x=1380, y=90)
self.save_button.place(x=1418, y=640)
self.draw_button.place(x=1460, y=232)
self.cut_button.place(x=1340, y=232)
self.reset_button.place(x=1385, y=190)
self.button_frame.pack()
self.reset()
self.editor_frame.place_forget()
# 保存已绘制的图像
def image_after_draw(self):
if self.lines_drawn:
self.save()
else:
messagebox.showinfo(title='无法保存!', message='您尚未在图像上绘制任何内容。')
# 关闭剪切功能
def cut_image_cancel(self):
self.cut_var.set(False) # 确保初始状态
self.cut_state = False
self.canvas.configure(cursor='arrow')
self.crop_save.place_forget()
self.crop_cancel.place_forget()
self.side_frame.place(x=1380, y=90)
self.save_button.place(x=1418, y=640)
self.draw_button.place(x=1460, y=232)
self.cut_button.place(x=1340, y=232)
self.reset_button.place(x=1385, y=190)
self.button_frame.pack()
self.reset()
self.warn_label.place_forget()
if self.rect:
self.canvas.delete(self.rect)
# 定义保存图片方法
def save(self):
image_path_object = filedialog.asksaveasfile(
defaultextension='*.jpg',
filetypes=[
("JPG文件", "*.jpg"),
("PNG文件", "*.png"),
("GIF文件", "*.gif"),
],
)
if image_path_object:
image_path = image_path_object.name
try:
self.image_copy.save(image_path, quality=95)
# 判断是绘图动态
if self.draw_state:
for line in self.lines_drawn:
self.canvas.delete(line)
self.lines_drawn = []
# 开始绘图
self.start_draw()
# 左点坐标
x = self.image_x_co
# 上点坐标
y = self.image_y_co + 43
# 右点坐标
x1 = self.image_x_co + self.current_resized_image_size[0]
# 下点坐标
y1 = self.image_y_co + self.current_resized_image_size[1] + 43
# crop()方法定义剪切区域(坐标为左、上、右、下四个点)
# save()方法保存结果图片,压缩质量为95%最高质量
ImageGrab.grab().crop((x, y, x1, y1)).save(image_path, quality=95)
# 判断是剪切动态
if self.cut_state:
self.canvas.delete(self.rect)
# 开始剪切
self.start_cut()
self.image_copy.save(image_path, quality=95)
self.image_paths.insert(self.current_index + 1, image_path)
self.current_index += 1
self.save_button.configure(state="normal")
self.show_image(image=image_path)
messagebox.showinfo('保存成功', f'图像已成功保存为“{image_path}”')
except Exception as e:
messagebox.showinfo('失败', f'图像保存失败:“{e}”')
self.check_var.set(False) # 确保初始状态
# 定义还原方法
def reset(self):
if self.original_image and not self.error:
if self.draw_state:
for line in self.lines_drawn:
self.canvas.delete(line)
self.lines_drawn = []
self.image_before_draw = self.image_copy
else:
current_original_image = self.image_paths[self.current_index]
self.show_image(image=current_original_image)
# 打开调整窗口
def open_adjustment_window(self):
if self.original_image and not self.error:
Adjustments(self, self.image_copy, self.modified_img, self.image_copy_resized, self.modified_img_resized)
# 打开过滤窗口
def open_filter_window(self):
if self.original_image and not self.error:
Filters(self, self.image_copy, self.modified_img, self.image_copy_resized, self.modified_img_resized)
# 点击关闭窗口触发函数
def window_close(self):
# 弹出确认对话框
if messagebox.askokcancel("退出", "你确定要退出吗?"):
# 点击确定后关闭窗口
self.destroy()
# 关于图片查看编辑程序
def about_image_editor(self):
about = tk.Tk()
about.title('关于图片查看编辑程序')
# 设置窗口居中
window_width = 610
window_height = 480
screen_width = about.winfo_screenwidth()
screen_height = about.winfo_screenheight()
x = (screen_width - window_width) / 2
y = (screen_height - window_height) / 2
about.geometry('%dx%d+%d+%d' % (window_width, window_height, x, y))
about.resizable(width=False, height=False)
about_frame = tk.Frame(about, width=610, height=480)
about_frame.pack()
tk.Label(about_frame, text='图片查看编辑器', font=("宋体", 16)).place(x=220, y=20)
tk.Label(about_frame, text='使用编程语言:Python', font=("宋体", 14)).place(x=50, y=90)
tk.Label(about_frame, text='导入图像:可导入后缀名为jpg、png、gif、bmp的图像', font=("宋体", 14)).place(x=50, y=150)
tk.Label(about_frame, text='保存图像:可以将图像保存为指定后缀名的图像文件', font=("宋体", 14)).place(x=50, y=210)
tk.Label(about_frame, text='编辑图像:打开指定图像,再点击“编辑图片”按钮', font=("宋体", 14)).place(x=50, y=270)
tk.Label(about_frame, text='剪切图像:按住鼠标左键拖动鼠标,选择想要剪切的部分', font=("宋体", 14)).place(x=50, y=330)
tk.Label(about_frame, text='创作者:www.pyhint.com', font=("宋体", 14)).place(x=50, y=390)
about.mainloop()
# 绘图程序讲解页面
def image_editor_help(self):
webbrowser.open("https://www.pyhint.com/article/163.html")
def mouse_not_hover(button, color=None):
if not color:
color = 'black'
button.configure(bg=color)
def mouse_hover(button, color=None):
if not color:
color = '#473f3f'
button.configure(bg=color)
# 图片调整方法
class Adjustments(tk.Toplevel):
def __init__(self, parent, image_copy, modified_img, image_copy_resized, modified_img_resized):
super().__init__(parent)
self.parent = parent
self.wm_overrideredirect(True)
self.grab_set()
self.winfo_parent()
self.geometry("245x260+1260+180")
self.image_copy = image_copy
self.modified_img = modified_img
self.image_copy_resized = image_copy_resized
self.modified_img_resized = modified_img_resized
self.modifications = {'contrast': 1.0, 'sharpness': 1.0, 'brightness': 1.0}
f1 = tk.Frame(self, borderwidth=1, relief='solid')
f1.pack(fill="both", expand=True)
tk.Label(f1, text="调整", font=('宋体', 14), pady=5).pack(fill="x")
contrast_l = tk.Label(f1, text="对比", relief="solid", font=('宋体', 12), width=10)
contrast_l.place(x=15, y=60)
self.contrast_b = tk.Scale(f1, from_=0, to=20, orient=tk.HORIZONTAL,
command=lambda e: self.adjust(e, 'contrast'),
)
self.contrast_b.place(x=120, y=40)
sharpness_l = tk.Label(f1, text="锐利", relief="solid", font=('宋体', 12), width=10)
sharpness_l.place(x=15, y=100)
self.sharpness_b = tk.Scale(f1, from_=0, to=30, orient=tk.HORIZONTAL,
command=lambda e: self.adjust(e, 'sharpness'),
)
self.sharpness_b.place(x=120, y=80)
#
brightness_l = tk.Label(f1, text="亮度", relief="solid", font=('宋体', 12), width=10)
brightness_l.place(x=15, y=140)
self.brightness_b = tk.Scale(f1, from_=0, to=20, orient=tk.HORIZONTAL,
command=lambda e: self.adjust(e, 'brightness'),
)
self.brightness_b.place(x=120, y=120)
cancel_button = tk.Button(f1, text="取消", command=self.cancel,
foreground="black", font=('宋体', 12), width=10,
background="#a8cdfe", cursor="hand2", borderwidth=4)
cancel_button.bind("<Enter>", lambda e: mouse_hover(cancel_button, color='#e6e6e6'))
cancel_button.bind("<Leave>", lambda e: mouse_not_hover(cancel_button, color='#a8cdfe'))
cancel_button.place(x=20, y=200)
apply_b = tk.Button(f1, text="确认", command=self.apply,
foreground="black", font=('宋体', 12), width=10,
background="#a8cdfe", cursor="hand2", borderwidth=4)
apply_b.bind("<Enter>", lambda e: mouse_hover(apply_b, color='#e6e6e6'))
apply_b.bind("<Leave>", lambda e: mouse_not_hover(apply_b, color='#a8cdfe'))
apply_b.place(x=130, y=200)
def adjust(self, e, changes):
im2 = self.image_copy
im2_resized = self.image_copy_resized
if 'original_image':
if self.modifications:
for modification in self.modifications.copy():
if changes == 'sharpness' and modification == 'sharpness':
del self.modifications[modification]
elif changes == 'contrast' and modification == 'contrast':
del self.modifications[modification]
elif changes == 'brightness' and modification == 'brightness':
del self.modifications[modification]
else:
property_ = modification
value = self.modifications[property_]
if property_ == 'sharpness':
enhancer = ImageEnhance.Sharpness(im2)
self.modified_img = enhancer.enhance(value)
enhancer = ImageEnhance.Sharpness(im2_resized)
self.modified_img_resized = enhancer.enhance(value)
im = ImageTk.PhotoImage(self.modified_img_resized)
self.parent.show_image(modified=im)
im2 = self.modified_img
im2_resized = self.modified_img_resized
elif property_ == 'contrast':
enhancer = ImageEnhance.Contrast(im2)
self.modified_img = enhancer.enhance(value)
enhancer = ImageEnhance.Contrast(im2_resized)
self.modified_img_resized = enhancer.enhance(value)
im = ImageTk.PhotoImage(self.modified_img_resized)
self.parent.show_image(modified=im)
im2 = self.modified_img
im2_resized = self.modified_img_resized
elif property_ == 'brightness':
enhancer = ImageEnhance.Brightness(im2)
self.modified_img = enhancer.enhance(value)
enhancer = ImageEnhance.Brightness(im2_resized)
self.modified_img_resized = enhancer.enhance(value)
im = ImageTk.PhotoImage(self.modified_img_resized)
self.parent.show_image(modified=im)
im2 = self.modified_img
im2_resized = self.modified_img_resized
if changes == 'sharpness':
self.modifications['sharpness'] = int(e) / 10
enhancer = ImageEnhance.Sharpness(self.modified_img)
self.modified_img = enhancer.enhance(int(e) / 10)
enhancer = ImageEnhance.Sharpness(self.modified_img_resized)
self.modified_img_resized = enhancer.enhance(int(e) / 10)
im = ImageTk.PhotoImage(self.modified_img_resized)
self.parent.show_image(modified=im)
elif changes == 'contrast':
self.modifications['contrast'] = int(e) / 10
enhancer = ImageEnhance.Contrast(self.modified_img)
self.modified_img = enhancer.enhance(int(e) / 10)
enhancer = ImageEnhance.Contrast(self.modified_img_resized)
self.modified_img_resized = enhancer.enhance(int(e) / 10)
im = ImageTk.PhotoImage(self.modified_img_resized)
self.parent.show_image(modified=im)
elif changes == 'brightness':
self.modifications['brightness'] = int(e) / 10
enhancer = ImageEnhance.Brightness(self.modified_img)
self.modified_img = enhancer.enhance(int(e) / 10)
enhancer = ImageEnhance.Brightness(self.modified_img_resized)
self.modified_img_resized = enhancer.enhance(int(e) / 10)
im = ImageTk.PhotoImage(self.modified_img_resized)
self.parent.show_image(modified=im)
def cancel(self):
self.grab_release()
im = ImageTk.PhotoImage(self.image_copy_resized)
self.parent.show_image(modified=im)
self.destroy()
def apply(self):
self.modifications = {'contrast': 1.0, 'sharpness': 1.0, 'brightness': 1.0}
self.grab_release()
for i in (self.brightness_b, self.contrast_b, self.sharpness_b):
if i.get() != 10:
self.parent.save_button.configure(state="normal")
break
self.parent.image_copy = self.modified_img
self.parent.image_copy_resized = self.modified_img_resized
im = ImageTk.PhotoImage(self.parent.image_copy_resized)
self.parent.show_image(modified=im)
self.destroy()
# 图片过滤方法
class Filters(tk.Toplevel):
def __init__(self, parent, image_copy, modified_img, image_copy_resized, modified_img_resized):
super().__init__(parent)
self.wm_overrideredirect(True)
self.grab_set()
self.winfo_parent()
self.geometry("230x280+1300+230")
self.filter_ = None
self.parent = parent
self.image_copy = image_copy
self.modified_img = modified_img
self.image_copy_resized = image_copy_resized
self.modified_img_resized = modified_img_resized
f1 = tk.Frame(self, borderwidth=1, relief='solid')
f1.pack(fill="both", expand=True)
tk.Label(f1, text="过滤", font=('宋体', 14), pady=5).pack(fill="x")
filters_frame = tk.Frame(f1)
filters_frame.pack(fill="both", expand=True)
emboss_button = tk.Button(filters_frame, text="浮雕", font=('宋体', 12),
command=lambda: self.filters(emboss_button),
padx=10, cursor="hand2", width=15, background="#e6e6e6")
emboss_button.bind("<Enter>", lambda e: mouse_hover(button=emboss_button, color='#aceafc'))
emboss_button.bind("<Leave>", lambda e: mouse_not_hover(button=emboss_button, color='#e6e6e6'))
emboss_button.place(x=40, y=20)
grey_button = tk.Button(filters_frame, text="灰度", font=('宋体', 12),
command=lambda: self.filters(grey_button),
padx=10, cursor="hand2", width=15, background="#e6e6e6")
grey_button.bind("<Enter>", lambda e: mouse_hover(button=grey_button, color='#aceafc'))
grey_button.bind("<Leave>", lambda e: mouse_not_hover(button=grey_button, color='#e6e6e6'))
grey_button.place(x=40, y=60)
negative_button = tk.Button(filters_frame, text="彩色反转", font=('宋体', 12),
command=lambda: self.filters(negative_button),
padx=10, cursor="hand2", width=15, background="#e6e6e6")
negative_button.bind("<Enter>", lambda e: mouse_hover(button=negative_button, color='#aceafc'))
negative_button.bind("<Leave>", lambda e: mouse_not_hover(button=negative_button, color='#e6e6e6'))
negative_button.place(x=40, y=100)
blur_button = tk.Button(filters_frame, text="高斯模糊", font=('宋体', 12),
command=lambda: self.filters(blur_button),
padx=10, cursor="hand2", width=15, background="#e6e6e6")
blur_button.bind("<Enter>", lambda e: mouse_hover(button=blur_button, color='#aceafc'))
blur_button.bind("<Leave>", lambda e: mouse_not_hover(button=blur_button, color='#e6e6e6'))
blur_button.place(x=40, y=140)
cancel_button = tk.Button(f1, text="取消", command=self.cancel, width=10, font=('宋体', 12),
background="#a8cdfe", cursor="hand2", borderwidth=4)
cancel_button.bind("<Enter>", lambda e: mouse_hover(button=cancel_button, color='#e6e6e6'))
cancel_button.bind("<Leave>", lambda e: mouse_not_hover(button=cancel_button, color='#a8cdfe'))
cancel_button.place(x=18, y=220)
apply_b = tk.Button(f1, text="确认", command=self.apply, width=10, font=('宋体', 12),
background="#a8cdfe", cursor="hand2", borderwidth=4)
apply_b.bind("<Enter>", lambda e: mouse_hover(button=apply_b, color='#e6e6e6'))
apply_b.bind("<Leave>", lambda e: mouse_not_hover(button=apply_b, color='#a8cdfe'))
apply_b.place(x=120, y=220)
def filters(self, button):
self.filter_ = button['text'].lower()
if self.filter_ == '浮雕':
self.modified_img = self.image_copy.filter(ImageFilter.EMBOSS)
self.modified_img_resized = self.image_copy_resized.filter(ImageFilter.EMBOSS)
im = ImageTk.PhotoImage(self.modified_img_resized)
self.parent.show_image(modified=im)
elif self.filter_ == '彩色反转':
self.modified_img = ImageOps.invert(self.image_copy)
self.modified_img_resized = ImageOps.invert(self.image_copy_resized)
im = ImageTk.PhotoImage(self.modified_img_resized)
self.parent.show_image(modified=im)
elif self.filter_ == '灰度':
self.modified_img = ImageOps.grayscale(self.image_copy)
self.modified_img_resized = ImageOps.grayscale(self.image_copy_resized)
im = ImageTk.PhotoImage(self.modified_img_resized)
self.parent.show_image(modified=im)
elif self.filter_ == '高斯模糊':
self.modified_img = self.image_copy.filter(ImageFilter.GaussianBlur)
self.modified_img_resized = self.image_copy_resized.filter(ImageFilter.GaussianBlur)
im = ImageTk.PhotoImage(self.modified_img_resized)
self.parent.show_image(modified=im)
def cancel(self):
self.grab_release()
im = ImageTk.PhotoImage(self.image_copy_resized)
self.parent.show_image(modified=im)
self.destroy()
def apply(self):
self.grab_release()
self.parent.image_copy = self.modified_img
self.parent.image_copy_resized = self.modified_img_resized
if self.filter_:
self.parent.save_button.configure(state="normal")
im = ImageTk.PhotoImage(self.parent.image_copy_resized)
self.parent.show_image(modified=im)
self.destroy()
# 当前模块直接被执行
if __name__ == '__main__':
# 创建主窗口
start = ImageEditor()