用python pillow实现GUI界面图片GUI处理工具该工具采用Tkinter构建GUI界面功能调整图片大小转换图片格式保存图片裁剪、旋转、翻转亮度 / 对比度 / 饱和度调整滤镜效果模糊、锐化、黑白等等运行截图安装依赖第三方库pip install pillowPython第三方模块(库、包)安装、卸载与查看及常见问题解决可参见 https://blog.csdn.net/cnds123/article/details/104393385Pillow 上手简单适合绝大多数日常图像处理场景比如裁剪、调色、加滤镜它是经典的 PILPython 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源码如下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(valuestr(self.org_w)) self.height_var tk.StringVar(valuestr(self.org_h)) self.quick_scale_var tk.StringVar(value100%) self.maintain_ratio tk.BooleanVar(valueTrue) self.resample tk.BooleanVar(valueTrue) 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, padx12, pady12) main_frame.pack(filltk.BOTH, expandTrue) left_frame tk.LabelFrame(main_frame, text尺寸设置, font(微软雅黑, 10)) left_frame.pack(sidetk.LEFT, filltk.BOTH, expandTrue, padx5, pady5) tk.Label(left_frame, text宽度, font(微软雅黑, 10)).grid(row0, column0, padx10, pady10, stickyw) tk.Entry(left_frame, textvariableself.width_var, font(微软雅黑, 10), width10).grid(row0, column1, padx5) tk.Label(left_frame, text像素).grid(row0, column2, stickyw) tk.Label(left_frame, text高度, font(微软雅黑, 10)).grid(row1, column0, padx10, pady8, stickyw) tk.Entry(left_frame, textvariableself.height_var, font(微软雅黑, 10), width10).grid(row1, column1, padx5) tk.Label(left_frame, text像素).grid(row1, column2, stickyw) tk.Label(left_frame, text快速缩放, font(微软雅黑, 10)).grid(row2, column0, padx10, pady10, stickyw) self.quick_combo ttk.Combobox(left_frame, textvariableself.quick_scale_var, valuesself.quick_scales, font(微软雅黑, 10), width7, statereadonly) self.quick_combo.grid(row2, column1, padx5, stickyw) tk.Checkbutton(left_frame, text保持宽高比, variableself.maintain_ratio, font(微软雅黑, 10)).grid(row3, column0, columnspan3, padx10, pady5, stickyw) tk.Checkbutton(left_frame, text高质量重采样, variableself.resample, font(微软雅黑, 10)).grid(row4, column0, columnspan3, padx10, pady5, stickyw) right_frame tk.Frame(main_frame) right_frame.pack(sidetk.RIGHT, filltk.Y, padx10) tk.Button(right_frame, text还原, width8, height2, commandself.restore_size).pack(pady6) tk.Button(right_frame, text确定, width8, height2, commandself.on_ok).pack(pady6) tk.Button(right_frame, text取消, width8, height2, commandself.top.destroy).pack(pady6) 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, eNone): 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, padx10, pady6) main_container.pack(filltk.BOTH, expandTrue) # 功能栏一行 func_bar tk.Frame(main_container) func_bar.pack(filltk.X, pady4) # 左文件操作 file_group tk.LabelFrame(func_bar, text文件操作, padx6, pady4) file_group.pack(sidetk.LEFT, padx4) tk.Button(file_group, text打开, width8, commandself.open_img).pack(sidetk.LEFT, padx3) tk.Button(file_group, text保存, width8, commandself.save_img).pack(sidetk.LEFT, padx3) tk.Button(file_group, text重置, width8, commandself.reset_original).pack(sidetk.LEFT, padx3) # 中几何变换 transform_group tk.LabelFrame(func_bar, text几何变换, padx6, pady4) transform_group.pack(sidetk.LEFT, padx4) tk.Button(transform_group, text调整大小, width8, commandself.open_resize).pack(sidetk.LEFT, padx3) tk.Button(transform_group, text裁剪, width6, commandself.start_crop).pack(sidetk.LEFT, padx3) tk.Button(transform_group, text左转, width6, commandlambda: self.rotate(90)).pack(sidetk.LEFT, padx3) tk.Button(transform_group, text右转, width6, commandlambda: self.rotate(-90)).pack(sidetk.LEFT, padx3) tk.Button(transform_group, text左右翻转, width8, commandself.flip_h).pack(sidetk.LEFT, padx3) tk.Button(transform_group, text上下翻转, width8, commandself.flip_v).pack(sidetk.LEFT, padx3) # 右滤镜 filter_group tk.LabelFrame(func_bar, text滤镜, padx6, pady4) filter_group.pack(sidetk.LEFT, padx4) tk.Button(filter_group, text模糊, width6, commandself.filter_blur).pack(sidetk.LEFT, padx3) tk.Button(filter_group, text锐化, width6, commandself.filter_sharpen).pack(sidetk.LEFT, padx3) tk.Button(filter_group, text黑白, width6, commandself.filter_gray).pack(sidetk.LEFT, padx3) tk.Button(filter_group, text复古, width6, commandself.filter_sepia).pack(sidetk.LEFT, padx3) tk.Button(filter_group, text浮雕, width6, commandself.filter_emboss).pack(sidetk.LEFT, padx3) # 图像增强 enhance_bar tk.Frame(main_container) enhance_bar.pack(filltk.X, pady6) enhance_group tk.LabelFrame(enhance_bar, text图像增强实时调节, padx10, pady6) enhance_group.pack(filltk.X, expandTrue) tk.Label(enhance_group, text亮度).grid(row0, column0, padx5) self.bright tk.Scale(enhance_group, from_0, to3, resolution0.1, length200, orienttk.HORIZONTAL, commandself.update_enhance) self.bright.grid(row0, column1, padx5) self.bright.set(1.0) tk.Label(enhance_group, text对比度).grid(row0, column2, padx5) self.contrast tk.Scale(enhance_group, from_0, to3, resolution0.1, length200, orienttk.HORIZONTAL, commandself.update_enhance) self.contrast.grid(row0, column3, padx5) self.contrast.set(1.0) tk.Label(enhance_group, text饱和度).grid(row0, column4, padx5) self.satur tk.Scale(enhance_group, from_0, to3, resolution0.1, length200, orienttk.HORIZONTAL, commandself.update_enhance) self.satur.grid(row0, column5, padx5) self.satur.set(1.0) # 图片显示区域 canvas_container tk.LabelFrame(main_container, text预览区域, padx6, pady4) canvas_container.pack(filltk.BOTH, expandTrue, pady4) self.canvas_frame tk.Frame(canvas_container) self.canvas_frame.pack(filltk.BOTH, expandTrue) self.sx tk.Scrollbar(self.canvas_frame, orienttk.HORIZONTAL) self.sy tk.Scrollbar(self.canvas_frame, orienttk.VERTICAL) self.canvas tk.Canvas(self.canvas_frame, bg#f5f5f5, xscrollcommandself.sx.set, yscrollcommandself.sy.set) self.sx.config(commandself.canvas.xview) self.sy.config(commandself.canvas.yview) self.sx.pack(sidetk.BOTTOM, filltk.X) self.sy.pack(sidetk.RIGHT, filltk.Y) self.canvas.pack(sidetk.LEFT, filltk.BOTH, expandTrue) 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, imageself.tk_img, anchortk.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, outlinered, dash(4, 2), width2) 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, expandTrue) 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, maskimg.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