用python + pillow实现GUI界面图片GUI处理工具
该工具采用Tkinter构建GUI界面,功能:
调整图片大小
转换图片格式保存
图片裁剪、旋转、翻转
亮度 / 对比度 / 饱和度调整
滤镜效果(模糊、锐化、黑白等)
等
运行截图:

安装依赖(第三方库):
pip install pillow
Python第三方模块(库、包)安装、卸载与查看及常见问题解决,可参见 https://blog.csdn.net/cnds123/article/details/104393385
Pillow 上手简单,适合绝大多数日常图像处理场景(比如裁剪、调色、加滤镜),它是经典的 PIL(Python Imaging Library)的活跃分支,也是 PIL 停止维护后的官方替代方案。需要注意的是:安装时(新手要特别注意)需要执行 pip install pillow 命令,但在代码中导入库时,必须使用 import PIL(而非 import pillow)------ 这是因为 Pillow 完全兼容 PIL 的 API 命名体系,保留了"PIL"这个导入标识,仅在包安装层面使用"pillow"名称。
pillow库(PIL库)的使用https://blog.csdn.net/cnds123/article/details/126141838
官网 https://pillow.readthedocs.io/en/stable/
中文参靠 https://pillow-docs-cn.readthedocs.io/zh-cn/latest/ 或 https://osgeo.cn/pillow/reference/index.html
源码如下:
python
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
from PIL import Image, ImageTk, ImageEnhance, ImageFilter
# ====================== 调整大小弹窗 ======================
class ResizePopup:
def __init__(self, parent, original_img, update_callback):
self.top = tk.Toplevel(parent)
self.top.title("调整大小")
self.top.geometry("560x310")
self.top.resizable(False, False)
self.top.transient(parent)
self.top.grab_set()
self.parent = parent
self.original_img = original_img
self.update_callback = update_callback
self.org_w, self.org_h = original_img.size
self.width_var = tk.StringVar(value=str(self.org_w))
self.height_var = tk.StringVar(value=str(self.org_h))
self.quick_scale_var = tk.StringVar(value="100%")
self.maintain_ratio = tk.BooleanVar(value=True)
self.resample = tk.BooleanVar(value=True)
self.quick_scales = ["25%", "50%", "75%", "100%", "125%", "150%", "200%"]
self._sync_locked = False
self.create_widgets()
self.bind_events()
def create_widgets(self):
main_frame = tk.Frame(self.top, padx=12, pady=12)
main_frame.pack(fill=tk.BOTH, expand=True)
left_frame = tk.LabelFrame(main_frame, text="尺寸设置", font=("微软雅黑", 10))
left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5)
tk.Label(left_frame, text="宽度", font=("微软雅黑", 10)).grid(row=0, column=0, padx=10, pady=10, sticky="w")
tk.Entry(left_frame, textvariable=self.width_var, font=("微软雅黑", 10), width=10).grid(row=0, column=1, padx=5)
tk.Label(left_frame, text="像素").grid(row=0, column=2, sticky="w")
tk.Label(left_frame, text="高度", font=("微软雅黑", 10)).grid(row=1, column=0, padx=10, pady=8, sticky="w")
tk.Entry(left_frame, textvariable=self.height_var, font=("微软雅黑", 10), width=10).grid(row=1, column=1, padx=5)
tk.Label(left_frame, text="像素").grid(row=1, column=2, sticky="w")
tk.Label(left_frame, text="快速缩放", font=("微软雅黑", 10)).grid(row=2, column=0, padx=10, pady=10, sticky="w")
self.quick_combo = ttk.Combobox(left_frame, textvariable=self.quick_scale_var, values=self.quick_scales, font=("微软雅黑", 10), width=7, state="readonly")
self.quick_combo.grid(row=2, column=1, padx=5, sticky="w")
tk.Checkbutton(left_frame, text="保持宽高比", variable=self.maintain_ratio, font=("微软雅黑", 10)).grid(row=3, column=0, columnspan=3, padx=10, pady=5, sticky="w")
tk.Checkbutton(left_frame, text="高质量重采样", variable=self.resample, font=("微软雅黑", 10)).grid(row=4, column=0, columnspan=3, padx=10, pady=5, sticky="w")
right_frame = tk.Frame(main_frame)
right_frame.pack(side=tk.RIGHT, fill=tk.Y, padx=10)
tk.Button(right_frame, text="还原", width=8, height=2, command=self.restore_size).pack(pady=6)
tk.Button(right_frame, text="确定", width=8, height=2, command=self.on_ok).pack(pady=6)
tk.Button(right_frame, text="取消", width=8, height=2, command=self.top.destroy).pack(pady=6)
def bind_events(self):
self.width_var.trace_add("write", self.sync_size)
self.height_var.trace_add("write", self.sync_size)
self.quick_combo.bind("<<ComboboxSelected>>", self.on_quick_scale)
def on_quick_scale(self, e=None):
try:
s = float(self.quick_scale_var.get().replace("%", "")) / 100
w, h = int(self.org_w * s), int(self.org_h * s)
self.width_var.set(str(w))
self.height_var.set(str(h))
except:
pass
def sync_size(self, *args):
if not self.maintain_ratio.get() or self._sync_locked:
return
try:
self._sync_locked = True
w = int(self.width_var.get())
h = int(w * self.org_h / self.org_w)
self.height_var.set(str(h))
except:
pass
finally:
self._sync_locked = False
def restore_size(self):
self.width_var.set(str(self.org_w))
self.height_var.set(str(self.org_h))
self.quick_scale_var.set("100%")
def on_ok(self):
try:
w, h = int(self.width_var.get()), int(self.height_var.get())
method = Image.Resampling.LANCZOS if self.resample.get() else Image.Resampling.NEAREST
self.update_callback(self.original_img.resize((w, h), method))
except:
messagebox.showerror("错误", "尺寸无效")
self.top.destroy()
# ====================== 主程序(美观紧凑版)======================
class ImageTool:
def __init__(self, root):
self.root = root
self.root.title("全能图片处理工具")
self.root.geometry("1180x800")
self.root.minsize(900, 650)
self.original_img = None
self.base_img = None
self.current_img = None
self.tk_img = None
self.is_cropping = False
self.crop_rect = None
self.build_ui()
def build_ui(self):
# ========== 主容器 ==========
main_container = tk.Frame(self.root, padx=10, pady=6)
main_container.pack(fill=tk.BOTH, expand=True)
# ========== 功能栏一行 ==========
func_bar = tk.Frame(main_container)
func_bar.pack(fill=tk.X, pady=4)
# 左:文件操作
file_group = tk.LabelFrame(func_bar, text="文件操作", padx=6, pady=4)
file_group.pack(side=tk.LEFT, padx=4)
tk.Button(file_group, text="打开", width=8, command=self.open_img).pack(side=tk.LEFT, padx=3)
tk.Button(file_group, text="保存", width=8, command=self.save_img).pack(side=tk.LEFT, padx=3)
tk.Button(file_group, text="重置", width=8, command=self.reset_original).pack(side=tk.LEFT, padx=3)
# 中:几何变换
transform_group = tk.LabelFrame(func_bar, text="几何变换", padx=6, pady=4)
transform_group.pack(side=tk.LEFT, padx=4)
tk.Button(transform_group, text="调整大小", width=8, command=self.open_resize).pack(side=tk.LEFT, padx=3)
tk.Button(transform_group, text="裁剪", width=6, command=self.start_crop).pack(side=tk.LEFT, padx=3)
tk.Button(transform_group, text="左转", width=6, command=lambda: self.rotate(90)).pack(side=tk.LEFT, padx=3)
tk.Button(transform_group, text="右转", width=6, command=lambda: self.rotate(-90)).pack(side=tk.LEFT, padx=3)
tk.Button(transform_group, text="左右翻转", width=8, command=self.flip_h).pack(side=tk.LEFT, padx=3)
tk.Button(transform_group, text="上下翻转", width=8, command=self.flip_v).pack(side=tk.LEFT, padx=3)
# 右:滤镜
filter_group = tk.LabelFrame(func_bar, text="滤镜", padx=6, pady=4)
filter_group.pack(side=tk.LEFT, padx=4)
tk.Button(filter_group, text="模糊", width=6, command=self.filter_blur).pack(side=tk.LEFT, padx=3)
tk.Button(filter_group, text="锐化", width=6, command=self.filter_sharpen).pack(side=tk.LEFT, padx=3)
tk.Button(filter_group, text="黑白", width=6, command=self.filter_gray).pack(side=tk.LEFT, padx=3)
tk.Button(filter_group, text="复古", width=6, command=self.filter_sepia).pack(side=tk.LEFT, padx=3)
tk.Button(filter_group, text="浮雕", width=6, command=self.filter_emboss).pack(side=tk.LEFT, padx=3)
# ========== 图像增强 ==========
enhance_bar = tk.Frame(main_container)
enhance_bar.pack(fill=tk.X, pady=6)
enhance_group = tk.LabelFrame(enhance_bar, text="图像增强(实时调节)", padx=10, pady=6)
enhance_group.pack(fill=tk.X, expand=True)
tk.Label(enhance_group, text="亮度").grid(row=0, column=0, padx=5)
self.bright = tk.Scale(enhance_group, from_=0, to=3, resolution=0.1, length=200, orient=tk.HORIZONTAL, command=self.update_enhance)
self.bright.grid(row=0, column=1, padx=5)
self.bright.set(1.0)
tk.Label(enhance_group, text="对比度").grid(row=0, column=2, padx=5)
self.contrast = tk.Scale(enhance_group, from_=0, to=3, resolution=0.1, length=200, orient=tk.HORIZONTAL, command=self.update_enhance)
self.contrast.grid(row=0, column=3, padx=5)
self.contrast.set(1.0)
tk.Label(enhance_group, text="饱和度").grid(row=0, column=4, padx=5)
self.satur = tk.Scale(enhance_group, from_=0, to=3, resolution=0.1, length=200, orient=tk.HORIZONTAL, command=self.update_enhance)
self.satur.grid(row=0, column=5, padx=5)
self.satur.set(1.0)
# ========== 图片显示区域 ==========
canvas_container = tk.LabelFrame(main_container, text="预览区域", padx=6, pady=4)
canvas_container.pack(fill=tk.BOTH, expand=True, pady=4)
self.canvas_frame = tk.Frame(canvas_container)
self.canvas_frame.pack(fill=tk.BOTH, expand=True)
self.sx = tk.Scrollbar(self.canvas_frame, orient=tk.HORIZONTAL)
self.sy = tk.Scrollbar(self.canvas_frame, orient=tk.VERTICAL)
self.canvas = tk.Canvas(self.canvas_frame, bg="#f5f5f5", xscrollcommand=self.sx.set, yscrollcommand=self.sy.set)
self.sx.config(command=self.canvas.xview)
self.sy.config(command=self.canvas.yview)
self.sx.pack(side=tk.BOTTOM, fill=tk.X)
self.sy.pack(side=tk.RIGHT, fill=tk.Y)
self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.canvas.bind("<ButtonPress-1>", self.on_crop_press)
self.canvas.bind("<B1-Motion>", self.on_crop_drag)
self.canvas.bind("<ButtonRelease-1>", self.on_crop_release)
# ========== 基础功能 ==========
def open_img(self):
path = filedialog.askopenfilename(filetypes=[
("所有图片", "*.jpg *.jpeg *.png *.bmp *.webp *.tiff"),
("JPG", "*.jpg;*.jpeg"), ("PNG", "*.png"), ("BMP", "*.bmp"), ("WebP", "*.webp")
])
if not path:
return
try:
self.original_img = Image.open(path).convert("RGBA")
self.base_img = self.original_img.copy()
self.update_enhance()
except Exception as e:
messagebox.showerror("错误", f"打开失败:{str(e)}")
def show_img(self):
if not self.current_img:
return
w, h = self.current_img.size
self.tk_img = ImageTk.PhotoImage(self.current_img)
self.canvas.delete("all")
self.canvas.create_image(0, 0, image=self.tk_img, anchor=tk.NW)
self.canvas.config(scrollregion=(0, 0, w, h))
def reset_original(self):
if self.original_img:
self.base_img = self.original_img.copy()
self.bright.set(1.0)
self.contrast.set(1.0)
self.satur.set(1.0)
self.update_enhance()
# ========== 裁剪 ==========
def start_crop(self):
if not self.base_img:
messagebox.showwarning("提示", "请先打开图片")
return
self.is_cropping = True
messagebox.showinfo("提示", "在图片上拖拽框选裁剪区域")
def on_crop_press(self, e):
if not self.is_cropping:
return
self.crop_start_x = self.canvas.canvasx(e.x)
self.crop_start_y = self.canvas.canvasy(e.y)
if self.crop_rect:
self.canvas.delete(self.crop_rect)
self.crop_rect = self.canvas.create_rectangle(0, 0, 0, 0, outline="red", dash=(4, 2), width=2)
def on_crop_drag(self, e):
if not self.is_cropping or not self.crop_rect:
return
cx = self.canvas.canvasx(e.x)
cy = self.canvas.canvasy(e.y)
self.canvas.coords(self.crop_rect, self.crop_start_x, self.crop_start_y, cx, cy)
def on_crop_release(self, e):
if not self.is_cropping or not self.crop_rect:
return
try:
x1, y1, x2, y2 = map(int, self.canvas.coords(self.crop_rect))
if x2 <= x1 or y2 <= y1:
self.canvas.delete(self.crop_rect)
self.is_cropping = False
return
self.base_img = self.base_img.crop((x1, y1, x2, y2))
self.update_enhance()
self.is_cropping = False
except:
self.canvas.delete(self.crop_rect)
self.is_cropping = False
# ========== 调整大小 ==========
def open_resize(self):
if not self.base_img:
messagebox.showwarning("提示", "请先打开图片")
return
ResizePopup(self.root, self.base_img, self.update_base_img)
def update_base_img(self, img):
self.base_img = img
self.update_enhance()
# ========== 旋转 / 翻转 ==========
def rotate(self, angle):
if self.base_img:
self.base_img = self.base_img.rotate(angle, expand=True)
self.update_enhance()
def flip_h(self):
if self.base_img:
self.base_img = self.base_img.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
self.update_enhance()
def flip_v(self):
if self.base_img:
self.base_img = self.base_img.transpose(Image.Transpose.FLIP_TOP_BOTTOM)
self.update_enhance()
# ========== 滤镜 ==========
def filter_blur(self):
if self.base_img:
self.base_img = self.base_img.filter(ImageFilter.GaussianBlur(2))
self.update_enhance()
def filter_sharpen(self):
if self.base_img:
self.base_img = self.base_img.filter(ImageFilter.SHARPEN)
self.update_enhance()
def filter_gray(self):
if self.base_img:
self.base_img = self.base_img.convert("L").convert("RGBA")
self.update_enhance()
def filter_sepia(self):
if self.base_img:
img = self.base_img.convert("RGB")
pixels = img.load()
w, h = img.size
for i in range(w):
for j in range(h):
r, g, b = img.getpixel((i, j))
tr = int(0.393 * r + 0.769 * g + 0.189 * b)
tg = int(0.349 * r + 0.686 * g + 0.168 * b)
tb = int(0.272 * r + 0.534 * g + 0.131 * b)
pixels[i, j] = (min(tr, 255), min(tg, 255), min(tb, 255))
self.base_img = img.convert("RGBA")
self.update_enhance()
def filter_emboss(self):
if self.base_img:
self.base_img = self.base_img.filter(ImageFilter.EMBOSS)
self.update_enhance()
# ========== 增强 ==========
def update_enhance(self, *args):
if not self.base_img:
return
img = self.base_img.copy()
img = ImageEnhance.Brightness(img).enhance(self.bright.get())
img = ImageEnhance.Contrast(img).enhance(self.contrast.get())
img = ImageEnhance.Color(img).enhance(self.satur.get())
self.current_img = img
self.show_img()
# ========== 保存 ==========
def save_img(self):
if not self.current_img:
messagebox.showwarning("提示", "无图片可保存")
return
path = filedialog.asksaveasfilename(defaultextension=".png", filetypes=[
("PNG", "*.png"), ("JPG", "*.jpg"), ("WebP", "*.webp"), ("BMP", "*.bmp")
])
if not path:
return
try:
img = self.current_img
if path.lower().endswith(("jpg", "jpeg")):
bg = Image.new("RGB", img.size, (255, 255, 255))
bg.paste(img, mask=img.split()[-1])
bg.save(path)
else:
img.save(path)
messagebox.showinfo("成功", "图片已保存")
except Exception as e:
messagebox.showerror("错误", f"保存失败:{str(e)}")
if __name__ == "__main__":
root = tk.Tk()
ImageTool(root)
root.mainloop()
附录
用python + PIL 实现图片格式转换工具 https://blog.csdn.net/cnds123/article/details/146922310