【Python】图片内容清除工具,附源码
功能:
- 快速清除一张图片不需要的内容,鼠标选中松开即清除
- 智能填充背景色,以选择边框占比多的颜色进行填充
- 对于同级目录多个要清除的图片,可直接“下一张”,节省操作时间
实现代码:
import tkinter as tk
from tkinter import filedialog, messagebox
from PIL import Image, ImageTk, ImageDraw
import os
import glob
from collections import Counter
class AdvancedImageEraser:
def __init__(self, root):
self.root = root
self.root.title("图片编辑工具")
self.root.geometry("1100x750")
self.root.configure(bg="#2c3e50")
self.root.minsize(800, 600) # 最小窗口尺寸
# 初始化变量
self.image_path = None
self.image_dir = None
self.image_files = []
self.current_index = -1
self.original_image = None
self.current_image = None
self.display_image = None
self.display_photo = None
self.start_x = None
self.start_y = None
self.rect = None
self.bg_color = (255, 255, 255) # 默认白色背景
self.scale_factor = 1.0 # 图片缩放因子
# 创建UI
self.create_widgets()
# 状态栏
self.status_var = tk.StringVar()
self.status_var.set("就绪 | 请打开一张图片")
status_bar = tk.Label(root, textvariable=self.status_var, bd=1, relief=tk.SUNKEN,
anchor=tk.W, bg="#34495e", fg="white")
status_bar.pack(side=tk.BOTTOM, fill=tk.X)
# 绑定窗口大小变化事件
self.root.bind("<Configure>", self.on_window_resize)
def create_widgets(self):
# 顶部工具栏
toolbar = tk.Frame(self.root, bg="#34495e", height=40)
toolbar.pack(side=tk.TOP, fill=tk.X)
# 按钮样式
btn_style = {"bg": "#3498db", "fg": "white", "bd": 0, "padx": 10, "pady": 5}
tk.Button(toolbar, text="打开图片", command=self.open_image, **btn_style).pack(side=tk.LEFT, padx=5)
tk.Button(toolbar, text="保存图片", command=self.save_image, **btn_style).pack(side=tk.LEFT, padx=5)
tk.Button(toolbar, text="重置", command=self.reset_image, **btn_style).pack(side=tk.LEFT, padx=5)
tk.Button(toolbar, text="上一张", command=self.prev_image, **btn_style).pack(side=tk.LEFT, padx=5)
tk.Button(toolbar, text="下一张", command=self.next_image, **btn_style).pack(side=tk.LEFT, padx=5)
# 图片显示区域 - 添加滚动条
image_frame = tk.Frame(self.root, bg="#2c3e50")
image_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 创建画布和滚动条
self.canvas = tk.Canvas(image_frame, bg="#34495e", bd=0, highlightthickness=0)
self.canvas.pack(fill=tk.BOTH, expand=True)
# 绑定鼠标事件
self.canvas.bind("<ButtonPress-1>", self.on_press)
self.canvas.bind("<B1-Motion>", self.on_drag)
self.canvas.bind("<ButtonRelease-1>", self.on_release)
# 操作说明
help_text = """
使用说明:
1. 点击"打开图片"按钮选择要编辑的图片
2. 在图片上按住鼠标左键并拖动选择要抹除的区域
3. 释放鼠标左键,选中的区域会被边缘主要颜色替换
4. 点击"保存图片"将修改保存到原文件
5. 使用"上一张"/"下一张"浏览同级目录图片
6. 图片会自动缩放以适应窗口大小
"""
tk.Label(image_frame, text=help_text, bg="#2c3e50", fg="#bdc3c7",
justify=tk.LEFT).pack(side=tk.BOTTOM, fill=tk.X, padx=10, pady=5)
def get_dominant_border_color(self, x1, y1, x2, y2):
"""获取选中区域边缘的主要颜色"""
if not self.original_image:
return (255, 255, 255) # 默认白色
# 将缩放后的坐标转换为原始坐标
orig_x1 = int(x1 / self.scale_factor)
orig_y1 = int(y1 / self.scale_factor)
orig_x2 = int(x2 / self.scale_factor)
orig_y2 = int(y2 / self.scale_factor)
# 确保坐标在图片范围内
width, height = self.original_image.size
orig_x1 = max(0, min(orig_x1, width))
orig_y1 = max(0, min(orig_y1, height))
orig_x2 = max(0, min(orig_x2, width))
orig_y2 = max(0, min(orig_y2, height))
# 收集边框区域的所有像素颜色
color_samples = []
border_size = 1 # 使用固定1像素边框
# 上边框 (从左到右)
for x in range(orig_x1, orig_x2, max(1, (orig_x2 - orig_x1) // 100)):
for y_offset in range(border_size):
y = orig_y1 + y_offset
if y < height:
try:
color = self.original_image.getpixel((x, y))
if len(color) == 4: # RGBA格式
color = color[:3] # 只取RGB
color_samples.append(color)
except:
pass
# 下边框 (从左到右)
for x in range(orig_x1, orig_x2, max(1, (orig_x2 - orig_x1) // 100)):
for y_offset in range(border_size):
y = orig_y2 - y_offset
if y >= 0:
try:
color = self.original_image.getpixel((x, y))
if len(color) == 4: # RGBA格式
color = color[:3] # 只取RGB
color_samples.append(color)
except:
pass
# 左边框 (从上到下)
for y in range(orig_y1, orig_y2, max(1, (orig_y2 - orig_y1) // 100)):
for x_offset in range(border_size):
x = orig_x1 + x_offset
if x < width:
try:
color = self.original_image.getpixel((x, y))
if len(color) == 4: # RGBA格式
color = color[:3] # 只取RGB
color_samples.append(color)
except:
pass
# 右边框 (从上到下)
for y in range(orig_y1, orig_y2, max(1, (orig_y2 - orig_y1) // 100)):
for x_offset in range(border_size):
x = orig_x2 - x_offset
if x >= 0:
try:
color = self.original_image.getpixel((x, y))
if len(color) == 4: # RGBA格式
color = color[:3] # 只取RGB
color_samples.append(color)
except:
pass
if not color_samples:
return (255, 255, 255) # 默认白色
# 找到最常见的颜色
color_counter = Counter(color_samples)
dominant_color = color_counter.most_common(1)[0][0]
return dominant_color
def resize_image(self, event=None):
"""调整图片大小以适应窗口"""
if not self.original_image:
return
# 获取可用空间 (减去状态栏、工具栏和边距)
toolbar_height = 40
statusbar_height = 20
margin = 40
available_width = self.root.winfo_width() - 40
available_height = self.root.winfo_height() - toolbar_height - statusbar_height - margin
# 计算缩放比例
img_width, img_height = self.original_image.size
width_ratio = available_width / img_width
height_ratio = available_height / img_height
self.scale_factor = min(width_ratio, height_ratio, 1.0) # 最大缩放100%
# 计算新尺寸
new_width = int(img_width * self.scale_factor)
new_height = int(img_height * self.scale_factor)
# 缩放图片
if self.scale_factor < 1.0:
resized_img = self.current_image.resize(
(new_width, new_height),
Image.Resampling.LANCZOS
)
else:
resized_img = self.current_image.copy()
# 更新显示
self.display_photo = ImageTk.PhotoImage(resized_img)
self.canvas.config(width=new_width, height=new_height)
self.canvas.create_image(0, 0, anchor=tk.NW, image=self.display_photo)
# 更新状态
if self.scale_factor < 1.0:
self.status_var.set(f"图片已缩放: {int(self.scale_factor*100)}% | 原始尺寸: {img_width}x{img_height}")
def open_image(self, path=None):
"""打开图片文件"""
if not path:
path = filedialog.askopenfilename(
filetypes=[("图片文件", "*.jpg *.jpeg *.png *.bmp *.gif")]
)
if not path:
return
try:
self.image_path = path
self.image_dir = os.path.dirname(path)
# 获取目录中所有图片文件
all_jpg = glob.glob(os.path.join(self.image_dir, "*.jpg"))
all_jpeg = glob.glob(os.path.join(self.image_dir, "*.jpeg"))
all_png = glob.glob(os.path.join(self.image_dir, "*.png"))
all_bmp = glob.glob(os.path.join(self.image_dir, "*.bmp"))
all_gif = glob.glob(os.path.join(self.image_dir, "*.gif"))
# 合并所有图片文件并排序
self.image_files = sorted(all_jpg + all_jpeg + all_png + all_bmp + all_gif)
# 获取当前图片索引
if self.image_files:
try:
self.current_index = self.image_files.index(path)
except ValueError:
# 如果文件不在列表中,尝试添加它
if os.path.exists(path):
self.image_files.append(path)
self.image_files = sorted(self.image_files)
self.current_index = self.image_files.index(path)
else:
self.current_index = -1
else:
self.current_index = -1
# 打开原始图片
self.original_image = Image.open(path).convert("RGB")
self.current_image = self.original_image.copy()
# 调整图片大小以适应窗口
self.resize_image()
# 更新状态
status = f"已打开: {os.path.basename(path)} | 尺寸: {self.original_image.width}x{self.original_image.height}"
if self.current_index >= 0 and len(self.image_files) > 1:
status += f" | 图片 {self.current_index+1}/{len(self.image_files)}"
self.status_var.set(status)
# 设置窗口标题
self.root.title(f"图片编辑工具 - {os.path.basename(path)}")
except Exception as e:
messagebox.showerror("错误", f"无法打开图片: {str(e)}")
self.status_var.set(f"错误: 无法打开图片 - {str(e)}")
def reset_image(self):
"""重置图片到原始状态"""
if self.original_image:
self.current_image = self.original_image.copy()
self.resize_image()
self.status_var.set("图片已重置")
def save_image(self):
"""保存图片到原文件"""
if not self.image_path:
messagebox.showwarning("警告", "请先打开一张图片")
return
if not self.current_image:
messagebox.showwarning("警告", "没有可保存的图片")
return
try:
# 保存到原文件
self.current_image.save(self.image_path)
# 更新状态
self.status_var.set(f"图片已保存到: {os.path.basename(self.image_path)}")
# 更新原始图片
self.original_image = self.current_image.copy()
messagebox.showinfo("保存成功", "图片已成功保存到原文件")
except Exception as e:
messagebox.showerror("错误", f"保存失败: {str(e)}")
self.status_var.set(f"保存失败: {str(e)}")
def prev_image(self):
"""打开同级目录的上一张图片"""
if not self.image_files or self.current_index < 0:
messagebox.showinfo("提示", "请先打开一张图片")
return
# 计算上一张图片索引
prev_index = (self.current_index - 1) % len(self.image_files)
# 打开上一张图片
self.open_image(self.image_files[prev_index])
def next_image(self):
"""打开同级目录的下一张图片"""
if not self.image_files or self.current_index < 0:
messagebox.showinfo("提示", "请先打开一张图片")
return
# 计算下一张图片索引
next_index = (self.current_index + 1) % len(self.image_files)
# 打开下一张图片
self.open_image(self.image_files[next_index])
def on_window_resize(self, event):
"""窗口大小变化时重新调整图片大小"""
if self.original_image:
self.resize_image()
def on_press(self, event):
"""鼠标按下事件"""
if not self.display_photo:
return
self.start_x = self.canvas.canvasx(event.x)
self.start_y = self.canvas.canvasy(event.y)
# 创建矩形选择框
self.rect = self.canvas.create_rectangle(
self.start_x, self.start_y, self.start_x, self.start_y,
outline="#e74c3c", width=2, dash=(4, 4)
)
def on_drag(self, event):
"""鼠标拖动事件"""
if self.rect is None:
return
cur_x = self.canvas.canvasx(event.x)
cur_y = self.canvas.canvasy(event.y)
# 更新矩形选择框
self.canvas.coords(self.rect, self.start_x, self.start_y, cur_x, cur_y)
def on_release(self, event):
"""鼠标释放事件"""
if self.rect is None or self.start_x is None or self.start_y is None:
return
end_x = self.canvas.canvasx(event.x)
end_y = self.canvas.canvasy(event.y)
# 确保坐标在图像范围内
canvas_width = self.canvas.winfo_width()
canvas_height = self.canvas.winfo_height()
x1 = max(0, min(self.start_x, end_x))
y1 = max(0, min(self.start_y, end_y))
x2 = min(canvas_width, max(self.start_x, end_x))
y2 = min(canvas_height, max(self.start_y, end_y))
# 确保有选择区域
if abs(x2 - x1) < 5 or abs(y2 - y1) < 5:
self.canvas.delete(self.rect)
self.rect = None
return
# 获取边缘主要颜色
self.bg_color = self.get_dominant_border_color(x1, y1, x2, y2)
# 计算原始坐标
orig_x1 = int(x1 / self.scale_factor)
orig_y1 = int(y1 / self.scale_factor)
orig_x2 = int(x2 / self.scale_factor)
orig_y2 = int(y2 / self.scale_factor)
# 在原始图像上绘制矩形
draw = ImageDraw.Draw(self.current_image)
draw.rectangle([orig_x1, orig_y1, orig_x2, orig_y2], fill=self.bg_color)
# 更新显示
self.resize_image()
# 删除选择框
self.canvas.delete(self.rect)
self.rect = None
# 更新状态
orig_width = orig_x2 - orig_x1
orig_height = orig_y2 - orig_y1
status = f"已抹除区域: ({orig_x1}, {orig_y1}) - ({orig_x2}, {orig_y2}) | 大小: {orig_width}x{orig_height} 像素"
status += f" | 背景色: RGB{self.bg_color}"
self.status_var.set(status)
if __name__ == "__main__":
root = tk.Tk()
app = AdvancedImageEraser(root)
root.mainloop()
工具效果: