当前位置: 首页 > news >正文

用python + pillow实现GUI界面图片GUI处理工具

用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

源码如下:

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

http://www.jsqmd.com/news/588745/

相关文章:

  • Condition底层机制剖析:多线程等待与通知机制 _
  • 南北阁 Nanbeige 4.1-3B 企业应用实战:客服预研、内部知识问答、合规本地化部署案例
  • 认知 对抗性雷达推理:逆向跟踪、认知识别与智能干扰设计——MATLAB实现
  • GlusterFS深度解析
  • Zrythm未来路线图:AI集成、云端协作与下一代音频技术
  • 【C++第二十三章】C++11
  • Python程序设计强基计划10讲 · 第六讲:面向对象编程(OOP)入门——封装、继承与多态
  • ESP32-C3、ESP32-S3、ESP32-C6 应该怎么选:面向定制固件项目的芯片判断
  • 软测学习笔记|2026.4.1|流程|分类|项目经验
  • ReTerraForged地形生成模组安装与配置全指南
  • 即插即用系列 | AAAI 2026 | SACF:光谱引导自适应跨层融合,强化目标内相关性与纹理细节,特征更精准! | 代码分享
  • Nano-Banana多场景落地:从电商详情页到产品培训手册的视觉赋能
  • Python程序设计强基计划10讲 · 第七讲:标准库精要——高效开发的秘密武器
  • 4 大类别 22 个高效的 Agentic Skills | 适用于 Claude、GPT
  • python decimal
  • Linux系统堆与栈原理深度剖析
  • 2025最权威的降重复率网站解析与推荐
  • 【数据结构与算法】第25篇:静态查找(一):顺序查找与折半查找
  • 文件存储Minio学习指南
  • NumPy张量缩并怎么用_np.einsum()爱因斯坦求和约定高级索引魔法
  • CMake赋能持续集成|自动化测试落地的进阶指南 ✨
  • 收藏!从房价暴跌看风口:小白/程序员必抓的AI大模型红利,零基础也能逆袭
  • CSS知识概述
  • 2026届毕业生推荐的五大AI论文网站实际效果
  • text2vec-base-chinese中文语义向量实战指南
  • 大语言模型部署时怎么解决显存爆炸问题
  • AquaticCLIP: A Vision-Language Foundation Model and Dataset for Underwater Scene Analysis
  • 【豆包从入门到精通】001、初识豆包:大模型时代的入门钥匙
  • 【教程4>第12章>第8节】基于FPGA的图像缩放实现——图像横向压缩仿真测试以及MATLAB辅助验证
  • AI算力芯片黑马!“图灵进化”完成新一轮数千万级别融资