活动介绍

帮我增加一个功能,框选好的尺寸可以进行保存在主界面,方便下次点击就能调用 import os import tkinter as tk from tkinter import filedialog, messagebox, ttk from PIL import Image, ImageTk, UnidentifiedImageError class ImageCropperApp: def __init__(self, root): self.root = root self.root.title("批量图片裁剪工具") self.root.geometry("900x600") self.root.minsize(800, 500) # 设置中文字体支持 self.style = ttk.Style() self.style.configure('.', font=('SimHei', 10)) # 初始化变量 self.images = [] # 存储所有待处理图片路径 self.current_image_index = 0 self.current_image = None self.current_photo = None self.canvas_image = None self.selection_start = None self.selection_rect = None self.crop_box = None self.output_dir = "" # 创建界面 self.create_widgets() def create_widgets(self): # 顶部控制区 top_frame = ttk.Frame(self.root, padding=10) top_frame.pack(fill=tk.X) ttk.Button(top_frame, text="选择图片文件夹", command=self.select_image_folder).pack(side=tk.LEFT, padx=5) ttk.Button(top_frame, text="设置输出文件夹", command=self.set_output_folder).pack(side=tk.LEFT, padx=5) self.status_var = tk.StringVar(value="请选择图片文件夹") ttk.Label(top_frame, textvariable=self.status_var).pack(side=tk.LEFT, padx=10) # 中部画布区 self.canvas_frame = ttk.Frame(self.root) self.canvas_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) self.canvas = tk.Canvas(self.canvas_frame, bg="white", highlightthickness=1) self.canvas.pack(fill=tk.BOTH, expand=True) # 底部控制区 bottom_frame = ttk.Frame(self.root, padding=10) bottom_frame.pack(fill=tk.X) ttk.Button(bottom_frame, text="上一张", command=self.prev_image).pack(side=tk.LEFT, padx=5) ttk.Button(bottom_frame, text="下一张", command=self.next_image).pack(side=tk.LEFT, padx=5) ttk.Label(bottom_frame, text="裁剪尺寸:").pack(side=tk.LEFT, padx=5) self.width_var = tk.StringVar(value="200") self.height_var = tk.StringVar(value="200") ttk.Entry(bottom_frame, textvariable=self.width_var, width=5).pack(side=tk.LEFT, padx=2) ttk.Label(bottom_frame, text="x").pack(side=tk.LEFT, padx=2) ttk.Entry(bottom_frame, textvariable=self.height_var, width=5).pack(side=tk.LEFT, padx=2) ttk.Button(bottom_frame, text="应用裁剪尺寸", command=self.apply_custom_crop_size).pack(side=tk.LEFT, padx=5) ttk.Button(bottom_frame, text="批量裁剪全部图片", command=self.batch_crop_images).pack(side=tk.LEFT, padx=5) self.image_counter = tk.StringVar(value="0/0") ttk.Label(bottom_frame, textvariable=self.image_counter).pack(side=tk.RIGHT, padx=10) # 绑定画布事件 self.canvas.bind("<Button-1>", self.on_canvas_click) self.canvas.bind("<B1-Motion>", self.on_canvas_drag) self.canvas.bind("<ButtonRelease-1>", self.on_canvas_release) # 滚动支持 self.canvas_frame.bind("<Configure>", self.on_canvas_configure) self.canvas.bind("<MouseWheel>", self.on_mousewheel) def select_image_folder(self): folder = filedialog.askdirectory(title="选择图片文件夹") if folder: self.images = [] for filename in os.listdir(folder): if filename.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif')): self.images.append(os.path.join(folder, filename)) if self.images: self.current_image_index = 0 self.load_image() self.status_var.set(f"已加载 {len(self.images)} 张图片") self.image_counter.set(f"{self.current_image_index + 1}/{len(self.images)}") else: messagebox.showinfo("提示", "所选文件夹中没有找到支持的图片格式") def set_output_folder(self): folder = filedialog.askdirectory(title="设置输出文件夹") if folder: self.output_dir = folder self.status_var.set(f"输出文件夹已设置: {folder}") def load_image(self): if 0 <= self.current_image_index < len(self.images): try: self.current_image = Image.open(self.images[self.current_image_index]) self.display_image() self.crop_box = None self.selection_start = None self.selection_rect = None except UnidentifiedImageError: messagebox.showerror("错误", f"无法打开图片: {self.images[self.current_image_index]}") self.next_image() def display_image(self): # 清除画布 self.canvas.delete("all") # 调整图片大小以适应画布 canvas_width = self.canvas.winfo_width() canvas_height = self.canvas.winfo_height() if canvas_width <= 1 or canvas_height <= 1: # 画布尚未完全初始化 return img_width, img_height = self.current_image.size ratio = min(canvas_width / img_width, canvas_height / img_height) display_width = int(img_width * ratio) display_height = int(img_height * ratio) self.current_photo = ImageTk.PhotoImage( self.current_image.resize((display_width, display_height), Image.LANCZOS)) self.canvas_image = self.canvas.create_image(canvas_width // 2, canvas_height // 2, image=self.current_photo) # 如果有裁剪框,重新绘制 if self.crop_box: x1, y1, x2, y2 = self.crop_box # 转换为画布坐标 x1_canvas = x1 * ratio + canvas_width // 2 - display_width // 2 y1_canvas = y1 * ratio + canvas_height // 2 - display_height // 2 x2_canvas = x2 * ratio + canvas_width // 2 - display_width // 2 y2_canvas = y2 * ratio + canvas_height // 2 - display_height // 2 self.selection_rect = self.canvas.create_rectangle( x1_canvas, y1_canvas, x2_canvas, y2_canvas, outline="red", width=2 ) def on_canvas_click(self, event): if not self.current_image: return self.selection_start = (event.x, event.y) # 如果已经有选择框,删除它 if self.selection_rect: self.canvas.delete(self.selection_rect) self.selection_rect = None def on_canvas_drag(self, event): if not self.current_image or not self.selection_start: return # 如果已经有选择框,删除它 if self.selection_rect: self.canvas.delete(self.selection_rect) # 创建新的选择框 x1, y1 = self.selection_start x2, y2 = event.x, event.y self.selection_rect = self.canvas.create_rectangle(x1, y1, x2, y2, outline="red", width=2) def on_canvas_release(self, event): if not self.current_image or not self.selection_start: return # 获取选择框的坐标 x1, y1 = self.selection_start x2, y2 = event.x, event.y # 确保坐标按正确顺序排列 x1, x2 = min(x1, x2), max(x1, x2) y1, y2 = min(y1, y2), max(y1, y2) # 转换为图像坐标 canvas_width = self.canvas.winfo_width() canvas_height = self.canvas.winfo_height() img_width, img_height = self.current_image.size ratio = min(canvas_width / img_width, canvas_height / img_height) img_x1 = int((x1 - canvas_width // 2 + img_width * ratio // 2) / ratio) img_y1 = int((y1 - canvas_height // 2 + img_height * ratio // 2) / ratio) img_x2 = int((x2 - canvas_width // 2 + img_width * ratio // 2) / ratio) img_y2 = int((y2 - canvas_height // 2 + img_height * ratio // 2) / ratio) # 确保坐标在图像范围内 img_x1 = max(0, min(img_x1, img_width)) img_y1 = max(0, min(img_y1, img_height)) img_x2 = max(0, min(img_x2, img_width)) img_y2 = max(0, min(img_y2, img_height)) # 保存裁剪框 self.crop_box = (img_x1, img_y1, img_x2, img_y2) # 显示裁剪尺寸信息 width = img_x2 - img_x1 height = img_y2 - img_y1 self.status_var.set(f"已选择裁剪区域: {width}x{height} 像素") def apply_custom_crop_size(self): try: width = int(self.width_var.get()) height = int(self.height_var.get()) if width <= 0 or height <= 0: messagebox.showerror("错误", "裁剪尺寸必须为正整数") return if not self.crop_box: messagebox.showinfo("提示", "请先在图片上选择一个裁剪区域") return # 获取当前裁剪框的中心点 x1, y1, x2, y2 = self.crop_box center_x = (x1 + x2) // 2 center_y = (y1 + y2) // 2 # 计算新的裁剪框坐标 new_x1 = max(0, center_x - width // 2) new_y1 = max(0, center_y - height // 2) new_x2 = min(self.current_image.width, new_x1 + width) new_y2 = min(self.current_image.height, new_y1 + height) # 调整裁剪框大小,保持中心点不变 if new_x2 - new_x1 < width: diff = width - (new_x2 - new_x1) new_x1 = max(0, new_x1 - diff // 2) new_x2 = new_x1 + width if new_y2 - new_y1 < height: diff = height - (new_y2 - new_y1) new_y1 = max(0, new_y1 - diff // 2) new_y2 = new_y1 + height self.crop_box = (new_x1, new_y1, new_x2, new_y2) # 刷新显示 self.display_image() self.status_var.set(f"已应用自定义裁剪尺寸: {width}x{height} 像素") except ValueError: messagebox.showerror("错误", "请输入有效的整数尺寸") def crop_current_image(self): if not self.crop_box: messagebox.showinfo("提示", "请先选择裁剪区域") return if not self.output_dir: messagebox.showinfo("提示", "请先设置输出文件夹") return x1, y1, x2, y2 = self.crop_box cropped_img = self.current_image.crop((x1, y1, x2, y2)) # 保存裁剪后的图片 filename = os.path.basename(self.images[self.current_image_index]) name, ext = os.path.splitext(filename) output_path = os.path.join(self.output_dir, f"{name}_cropped{ext}") try: # 保存时保持原画质 if ext.lower() in ('.jpg', '.jpeg'): cropped_img.save(output_path, quality=100, subsampling=0) else: cropped_img.save(output_path) self.status_var.set(f"已裁剪并保存图片: {output_path}") return True except Exception as e: messagebox.showerror("错误", f"保存图片时出错: {str(e)}") return False def batch_crop_images(self): if not self.crop_box: messagebox.showinfo("提示", "请先在第一张图片上选择裁剪区域") return if not self.output_dir: messagebox.showinfo("提示", "请先设置输出文件夹") return total = len(self.images) success_count = 0 error_count = 0 # 保存当前状态 current_index = self.current_image_index original_crop_box = self.crop_box # 批量处理所有图片 for i, img_path in enumerate(self.images): try: # 更新状态 self.status_var.set(f"正在处理图片 {i + 1}/{total}...") self.root.update() # 打开图片 self.current_image = Image.open(img_path) # 使用相同的裁剪框 self.crop_box = original_crop_box # 裁剪并保存 x1, y1, x2, y2 = self.crop_box cropped_img = self.current_image.crop((x1, y1, x2, y2)) filename = os.path.basename(img_path) name, ext = os.path.splitext(filename) output_path = os.path.join(self.output_dir, f"{name}_cropped{ext}") if ext.lower() in ('.jpg', '.jpeg'): cropped_img.save(output_path, quality=100, subsampling=0) else: cropped_img.save(output_path) success_count += 1 except Exception as e: error_count += 1 print(f"处理图片 {img_path} 时出错: {str(e)}") # 恢复状态 self.current_image_index = current_index self.load_image() # 显示结果 result_msg = f"批量裁剪完成!\n成功: {success_count} 张\n失败: {error_count} 张" self.status_var.set(result_msg) messagebox.showinfo("批量裁剪结果", result_msg) def prev_image(self): if self.images and self.current_image_index > 0: self.current_image_index -= 1 self.load_image() self.image_counter.set(f"{self.current_image_index + 1}/{len(self.images)}") def next_image(self): if self.images and self.current_image_index < len(self.images) - 1: self.current_image_index += 1 self.load_image() self.image_counter.set(f"{self.current_image_index + 1}/{len(self.images)}") def on_canvas_configure(self, event): # 画布大小改变时重绘图像 if self.current_image: self.display_image() def on_mousewheel(self, event): # 处理鼠标滚轮缩放 if self.current_image: # 缩放逻辑可以在这里实现 pass if __name__ == "__main__": root = tk.Tk() app = ImageCropperApp(root) root.mainloop()

时间: 2025-07-20 13:19:51 浏览: 19
<think>我们需要添加一个功能:允许用户保存当前设置的裁剪框尺寸(以及位置?),并在主界面提供快速调用这些保存的尺寸的按钮。 但是,根据需求描述,只要求保存尺寸(宽高),不要求保存位置。因为位置是针对每张图片可能不同的,而尺寸是通用的。 所以,我们计划: 1. 在主界面上增加一个“保存当前尺寸”的按钮,点击后会将当前裁剪框的宽高保存到一个列表中(同时可以给这个尺寸命名?)。 由于需求是方便下次点击调用,我们可以让用户输入一个名称,也可以自动生成名称(如“尺寸1”、“尺寸2”)。但为了用户友好,我们让用户输入一个名称。 2. 在主界面上显示已保存的尺寸列表(比如用下拉框或按钮列表),用户点击某个已保存的尺寸,就会将当前裁剪框的尺寸设置为该尺寸(同时保持当前裁剪框的中心位置不变?)。 注意:这里我们只设置尺寸,不改变位置。因为位置是用户每张图片自己选择的。所以,我们只需要将当前裁剪框的尺寸改为保存的尺寸,而中心点不变(这样用户可以在同一位置用不同尺寸裁剪)。 但是,原功能中已经有一个“应用裁剪尺寸”按钮,它根据输入框的宽高来调整当前裁剪框的尺寸(中心不变)。所以,我们保存尺寸后,当用户点击某个保存的尺寸,我们只需要将输入框的宽高设置为该尺寸,然后调用`apply_custom_crop_size`方法即可。 因此,我们可以这样设计: - 添加一个“保存当前尺寸”按钮,点击后弹出对话框让用户输入尺寸名称(同时显示当前裁剪框的宽高)。 - 将保存的尺寸以列表形式存储(在内存中,同时可以持久化到文件,这样下次启动也能用?需求没有明确,但为了下次启动也能用,我们保存到文件)。 - 在界面上显示保存的尺寸列表(比如用一组按钮,或者一个下拉菜单,或者一个列表框)。这里为了快速调用,使用一组按钮(水平排列或垂直排列)可能更直观,但考虑到可能保存多个,我们使用一个下拉框(Combobox)和一个“应用”按钮,或者直接点击下拉框的选项就应用?或者使用一组按钮放在一个可滚动的区域。 考虑到界面空间,我们在顶部控制区增加一个区域来显示保存的尺寸按钮。但是顶部空间有限,我们可以放在底部控制区上面,或者左侧边栏。 由于我们的界面目前是上下结构,我们可以将底部控制区上面增加一个区域(比如在画布下面,底部控制区上面)作为“保存尺寸”的按钮区。 具体修改步骤: 1. 在类中增加一个列表属性来存储保存的尺寸,每个元素是一个字典:{"name": "名称", "width": 宽, "height": 高} 2. 增加一个保存尺寸的方法:`save_current_size`,弹出对话框让用户输入名称,然后保存当前裁剪框的尺寸(如果没有裁剪框,则提示先选择)。 3. 增加一个应用保存尺寸的方法:`apply_saved_size`,当用户选择一个保存的尺寸时,将输入框的宽高设置为该尺寸,并调用`apply_custom_crop_size`方法(这样就会调整当前裁剪框的尺寸)。 4. 在界面上增加一个区域(Frame)用于显示保存的尺寸按钮(或者一个下拉框和按钮)。为了节省空间,我们可以用下拉框(Combobox)和一个“应用”按钮,再加上一个“保存”按钮。 但是,为了更直观的快速调用,我们可以用一组按钮,每个按钮显示一个保存的尺寸名称(如“名称(宽x高)”),点击按钮就应用该尺寸。 由于保存的尺寸可能很多,我们可以用一个可滚动的区域来放置这些按钮。 考虑到界面复杂度,我们这里采用一个下拉框(显示保存的尺寸名称)和两个按钮(“应用”和“保存”)。另外,我们还需要一个“删除”按钮来删除已保存的尺寸?但是需求没有要求,可以先不做。 因此,我们在顶部控制区增加如下控件: - 一个标签:“预设尺寸” - 一个Combobox,用于选择保存的尺寸 - 一个“保存当前尺寸”按钮(已经有一个“保存”按钮了,但为了避免混淆,我们叫“保存尺寸”) - 一个“应用尺寸”按钮 另外,我们需要将保存的尺寸持久化到文件,这样下次启动还能用。我们可以用一个json文件保存在程序所在目录。 具体代码修改: 1. 在`__init__`方法中: self.saved_sizes = [] # 存储保存的尺寸列表 self.load_saved_sizes() # 从文件加载保存的尺寸 2. 在`create_widgets`方法中,在顶部控制区(top_frame)增加控件: # 在顶部控制区增加预设尺寸相关控件 ttk.Label(top_frame, text="预设尺寸:").pack(side=tk.LEFT, padx=5) self.size_combo = ttk.Combobox(top_frame, state="readonly", width=15) self.size_combo.pack(side=tk.LEFT, padx=5) ttk.Button(top_frame, text="应用尺寸", command=self.apply_saved_size).pack(side=tk.LEFT, padx=2) ttk.Button(top_frame, text="保存当前尺寸", command=self.save_current_size).pack(side=tk.LEFT, padx=2) 然后,在初始化部分更新下拉框的列表: self.update_size_combo() 3. 实现`load_saved_sizes`和`save_sizes_to_file`方法,用于从文件加载和保存尺寸列表。文件名为"crop_sizes.json"。 4. 实现`update_size_combo`方法,将self.saved_sizes中的每个尺寸的名称组成列表,设置到下拉框中。 5. 实现`save_current_size`方法: - 检查是否有裁剪框,没有则提示。 - 计算当前裁剪框的宽高(x2-x1, y2-y1)。 - 弹出输入对话框(使用simpledialog.askstring)让用户输入名称。 - 如果输入的名称不为空,且不与已有名称重复(如果重复,可以提示覆盖?或者我们允许重复,但应用时可能会有多个相同名称?为了避免混淆,我们不允许重复,如果重复则提示用户重新输入或覆盖)。 - 将新的尺寸添加到saved_sizes列表中(如果重复,先删除旧的,再添加新的;或者不允许重复,则让用户重新输入)。 - 更新下拉框,并保存到文件。 6. 实现`apply_saved_size`方法: - 从下拉框中获取当前选中的尺寸名称。 - 在saved_sizes中查找该名称对应的尺寸。 - 将宽度和高度设置到输入框(width_var和height_var)中。 - 调用`apply_custom_crop_size`方法(注意:这个方法会调整当前裁剪框的尺寸,保持中心不变)。 7. 注意:在应用尺寸时,如果当前没有裁剪框,那么先要创建一个默认的裁剪框吗?原逻辑中,如果没有裁剪框,点击“应用裁剪尺寸”会提示先选择区域。所以我们这里也遵循同样的逻辑,即如果没有裁剪框,则提示先选择区域。 但是,我们可以在应用保存尺寸的时候,如果没有裁剪框,我们可以自动创建一个以图片中心为基准的裁剪框?这样更友好。但原功能没有,为了保持一致,我们暂时不自动创建,而是提示用户。 考虑到代码复用,我们可以修改`apply_custom_crop_size`方法,使其可以在没有裁剪框时,使用图片中心来创建裁剪框。但原需求没有要求,我们这里先不改变原逻辑,所以当没有裁剪框时,点击“应用尺寸”会弹出提示。 因此,在`apply_saved_size`方法中,我们只是设置了输入框的值,并调用了`apply_custom_crop_size`,所以它会检查是否有裁剪框。 8. 另外,在批量裁剪时,我们使用的是同一个裁剪框(位置和尺寸),所以保存的尺寸只用于调整当前裁剪框,不会影响批量裁剪(因为批量裁剪使用的是当前裁剪框,而我们在调整尺寸后,当前裁剪框已经被更新了)。 下面我们开始修改代码。 注意:持久化文件操作,我们使用json。文件保存在当前工作目录下的"crop_sizes.json"。 修改后的代码:</think>我将为您添加保存和调用裁剪尺寸的功能。以下是修改后的代码,新增功能包括: 1. 保存当前裁剪框尺寸到预设列表 2. 在主界面显示预设尺寸按钮 3. 点击预设按钮自动应用对应尺寸 ```python import os import json import tkinter as tk from tkinter import filedialog, messagebox, ttk from PIL import Image, ImageTk, UnidentifiedImageError class ImageCropperApp: def __init__(self, root): self.root = root self.root.title("批量图片裁剪工具") self.root.geometry("900x600") self.root.minsize(800, 500) # 设置中文字体支持 self.style = ttk.Style() self.style.configure('.', font=('SimHei', 10)) # 初始化变量 self.images = [] # 存储所有待处理图片路径 self.current_image_index = 0 self.current_image = None self.current_photo = None self.canvas_image = None self.selection_start = None self.selection_rect = None self.crop_box = None self.output_dir = "" self.saved_sizes = [] # 存储保存的尺寸预设 self.PRESETS_FILE = "crop_presets.json" # 预设尺寸保存文件 # 创建界面 self.create_widgets() # 加载保存的尺寸预设 self.load_saved_sizes() def create_widgets(self): # 顶部控制区 top_frame = ttk.Frame(self.root, padding=10) top_frame.pack(fill=tk.X) ttk.Button(top_frame, text="选择图片文件夹", command=self.select_image_folder).pack(side=tk.LEFT, padx=5) ttk.Button(top_frame, text="设置输出文件夹", command=self.set_output_folder).pack(side=tk.LEFT, padx=5) self.status_var = tk.StringVar(value="请选择图片文件夹") ttk.Label(top_frame, textvariable=self.status_var).pack(side=tk.LEFT, padx=10) # 预设尺寸区域 presets_frame = ttk.LabelFrame(self.root, text="预设尺寸", padding=5) presets_frame.pack(fill=tk.X, padx=10, pady=5) # 使用Canvas和Frame实现可滚动的预设区域 self.presets_canvas = tk.Canvas(presets_frame, height=40, highlightthickness=0) self.presets_scrollbar = ttk.Scrollbar(presets_frame, orient="horizontal", command=self.presets_canvas.xview) self.presets_canvas.configure(xscrollcommand=self.presets_scrollbar.set) self.presets_container = ttk.Frame(self.presets_canvas) self.presets_container.bind("<Configure>", lambda e: self.presets_canvas.configure(scrollregion=self.presets_canvas.bbox("all"))) self.presets_canvas.create_window((0, 0), window=self.presets_container, anchor="nw") self.presets_scrollbar.pack(side="bottom", fill="x") self.presets_canvas.pack(side="top", fill="x", expand=True) # 中部画布区 self.canvas_frame = ttk.Frame(self.root) self.canvas_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) self.canvas = tk.Canvas(self.canvas_frame, bg="white", highlightthickness=1) self.canvas.pack(fill=tk.BOTH, expand=True) # 底部控制区 bottom_frame = ttk.Frame(self.root, padding=10) bottom_frame.pack(fill=tk.X) ttk.Button(bottom_frame, text="上一张", command=self.prev_image).pack(side=tk.LEFT, padx=5) ttk.Button(bottom_frame, text="下一张", command=self.next_image).pack(side=tk.LEFT, padx=5) ttk.Label(bottom_frame, text="裁剪尺寸:").pack(side=tk.LEFT, padx=5) self.width_var = tk.StringVar(value="200") self.height_var = tk.StringVar(value="200") ttk.Entry(bottom_frame, textvariable=self.width_var, width=5).pack(side=tk.LEFT, padx=2) ttk.Label(bottom_frame, text="x").pack(side=tk.LEFT, padx=2) ttk.Entry(bottom_frame, textvariable=self.height_var, width=5).pack(side=tk.LEFT, padx=2) ttk.Button(bottom_frame, text="应用裁剪尺寸", command=self.apply_custom_crop_size).pack(side=tk.LEFT, padx=5) ttk.Button(bottom_frame, text="保存当前尺寸", command=self.save_current_size).pack(side=tk.LEFT, padx=5) # 新增按钮 ttk.Button(bottom_frame, text="批量裁剪全部图片", command=self.batch_crop_images).pack(side=tk.LEFT, padx=5) self.image_counter = tk.StringVar(value="0/0") ttk.Label(bottom_frame, textvariable=self.image_counter).pack(side=tk.RIGHT, padx=10) # 绑定画布事件 self.canvas.bind("<Button-1>", self.on_canvas_click) self.canvas.bind("<B1-Motion>", self.on_canvas_drag) self.canvas.bind("<ButtonRelease-1>", self.on_canvas_release) # 滚动支持 self.canvas_frame.bind("<Configure>", self.on_canvas_configure) self.canvas.bind("<MouseWheel>", self.on_mousewheel) # 新增功能:加载保存的尺寸预设 def load_saved_sizes(self): if os.path.exists(self.PRESETS_FILE): try: with open(self.PRESETS_FILE, 'r') as f: self.saved_sizes = json.load(f) self.update_presets_buttons() except: self.saved_sizes = [] else: self.saved_sizes = [] # 新增功能:保存尺寸预设到文件 def save_sizes_to_file(self): with open(self.PRESETS_FILE, 'w') as f: json.dump(self.saved_sizes, f) # 新增功能:更新预设尺寸按钮 def update_presets_buttons(self): # 清除现有按钮 for widget in self.presets_container.winfo_children(): widget.destroy() # 创建新按钮 for i, size in enumerate(self.saved_sizes): w, h = size btn = ttk.Button( self.presets_container, text=f"{w}x{h}", width=8, command=lambda w=w, h=h: self.apply_saved_size(w, h) ) btn.pack(side=tk.LEFT, padx=5, pady=2) # 添加一个清除按钮 clear_btn = ttk.Button( self.presets_container, text="清除预设", width=8, command=self.clear_saved_sizes ) clear_btn.pack(side=tk.LEFT, padx=5, pady=2) # 新增功能:应用保存的尺寸 def apply_saved_size(self, width, height): if not self.crop_box: messagebox.showinfo("提示", "请先在图片上选择一个裁剪区域") return # 更新输入框 self.width_var.set(str(width)) self.height_var.set(str(height)) # 应用尺寸 self.apply_custom_crop_size() self.status_var.set(f"已应用预设尺寸: {width}x{height} 像素") # 新增功能:清除所有保存的尺寸 def clear_saved_sizes(self): if self.saved_sizes: if messagebox.askyesno("确认", "确定要清除所有保存的尺寸预设吗?"): self.saved_sizes = [] self.save_sizes_to_file() self.update_presets_buttons() # 新增功能:保存当前尺寸 def save_current_size(self): if not self.crop_box: messagebox.showinfo("提示", "请先在图片上选择一个裁剪区域") return # 获取当前尺寸 x1, y1, x2, y2 = self.crop_box width = x2 - x1 height = y2 - y1 # 创建尺寸元组 size = (width, height) # 检查是否已存在相同尺寸 if size in self.saved_sizes: messagebox.showinfo("提示", "该尺寸已保存") return # 添加到保存列表 self.saved_sizes.append(size) self.save_sizes_to_file() self.update_presets_buttons() self.status_var.set(f"已保存尺寸: {width}x{height} 像素") # 以下为原有方法保持不变,仅添加了注释 def select_image_folder(self): # ...(原有代码不变)... def set_output_folder(self): # ...(原有代码不变)... def load_image(self): # ...(原有代码不变)... def display_image(self): # ...(原有代码不变)... def on_canvas_click(self, event): # ...(原有代码不变)... def on_canvas_drag(self, event): # ...(原有代码不变)... def on_canvas_release(self, event): # ...(原有代码不变)... def apply_custom_crop_size(self): # ...(原有代码不变)... def crop_current_image(self): # ...(原有代码不变)... def batch_crop_images(self): # ...(原有代码不变)... def prev_image(self): # ...(原有代码不变)... def next_image(self): # ...(原有代码不变)... def on_canvas_configure(self, event): # ...(原有代码不变)... def on_mousewheel(self, event): # ...(原有代码不变)... if __name__ == "__main__": root = tk.Tk() app = ImageCropperApp(root) root.mainloop() ``` ### 新增功能详细说明: 1. **尺寸预设存储**: - 使用JSON文件(`crop_presets.json`)保存所有预设尺寸 - `load_saved_sizes()`方法在程序启动时加载预设 - `save_sizes_to_file()`方法在保存新尺寸时更新文件 2. **预设尺寸界面**: - 在主界面顶部添加预设尺寸区域(可水平滚动) - 每个预设尺寸显示为按钮,格式为"宽x高" - 添加"清除预设"按钮移除所有保存的尺寸 3. **功能实现**: - **保存当前尺寸**:点击"保存当前尺寸"按钮将当前裁剪框尺寸添加到预设 - **应用预设尺寸**:点击预设按钮自动设置尺寸并应用到当前裁剪框 - **清除预设**:一键移除所有保存的尺寸 4. **用户体验优化**: - 检查重复尺寸避免重复保存 - 添加确认对话框防止误操作 - 状态栏显示操作反馈 ### 使用流程: 1. 选择图片并绘制裁剪框 2. 点击"保存当前尺寸"将尺寸添加到预设区 3. 后续使用时,直接点击预设按钮即可应用对应尺寸 4. 点击"清除预设"可移除所有保存的尺寸 ### 注意事项: 1. 预设尺寸存储在程序同目录的`crop_presets.json`文件中 2. 预设尺寸仅保存宽高信息,不包含位置信息 3. 应用预设尺寸时需要先有一个裁剪框作为基准位置
阅读全文

相关推荐

在不影响基本功能的前提下,优化下面的gui界面样式使其更加优美好看,优化好后给我返回完整的可执行的python代码# coding=utf-8 import cv2 import os import numpy as np from time import time import tkinter as tk from tkinter import ttk, messagebox, simpledialog from PIL import Image, ImageTk import threading # 常量定义 FACE_DB = "face_database" HAAR_CASCADE = cv2.data.haarcascades + 'haarcascade_frontalface_default.xml' SAMPLE_SIZE = 30 MIN_CONFIDENCE = 65 class FaceSystem: def __init__(self): self.face_cascade = cv2.CascadeClassifier(HAAR_CASCADE) self.models = self._load_models() self.current_frame = None self.update_callback = None self.running = False def _load_models(self): models = {} if os.path.exists(FACE_DB): for f in os.listdir(FACE_DB): if f.endswith(".yml"): name = os.path.splitext(f)[0] recognizer = cv2.face.LBPHFaceRecognizer_create() recognizer.read(os.path.join(FACE_DB, f)) models[name] = recognizer return models def register(self, name, source=0): self.running = True user_id = len(self.models) + 1 samples, labels = [], [] cap = cv2.VideoCapture(source) last_cap = 0 while len(samples) < SAMPLE_SIZE and self.running: ret, frame = cap.read() if not ret: continue gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces = self.face_cascade.detectMultiScale( gray, scaleFactor=1.05, minNeighbors=6, minSize=(60, 60)) if time() - last_cap > 0.5 and len(faces) == 1: (x, y, w, h) = faces[0] face_roi = gray[y:y + h, x:x + w] samples.append(face_roi) labels.append(user_id) last_cap = time() self._draw_ui(frame, f"采集进度: {len(samples)}/{SAMPLE_SIZE}") if self.update_callback: self.update_callback(f

最新推荐

recommend-type

Python使用tkinter库实现文本显示用户输入功能示例

在Python的图形用户界面(GUI)编程中,tkinter库是一个非常常用的选择,它提供了创建交互式窗口应用程序的能力。在给定的示例中,我们看到了如何使用tkinter来实现一个简单的计算器,其中包含了文本显示用户输入的...
recommend-type

Python实现定时自动关闭的tkinter窗口方法

在Python的图形用户界面开发中,Tkinter是一个非常常用的标准库,它提供了创建窗口和交互式控件的能力。本文将详细介绍如何使用Tkinter和多线程来实现一个定时自动关闭的窗口。 首先,我们需要导入所需的模块。...
recommend-type

详解python tkinter包获取本地绝对路径(以获取图片并展示)

在Python编程中,Tkinter库是内置的GUI(图形用户界面)工具包,它允许开发者创建桌面应用程序。本文将深入探讨如何使用Tkinter获取本地绝对路径,并通过该路径加载和展示图片。 首先,我们导入所需的模块:`...
recommend-type

Mockingbird v2:PocketMine-MP新防作弊机制详解

标题和描述中所涉及的知识点如下: 1. Mockingbird反作弊系统: Mockingbird是一个正在开发中的反作弊系统,专门针对PocketMine-MP服务器。PocketMine-MP是Minecraft Pocket Edition(Minecraft PE)的一个服务器软件,允许玩家在移动平台上共同游戏。随着游戏的普及,作弊问题也随之而来,因此Mockingbird的出现正是为了应对这种情况。 2. Mockingbird的版本迭代: 从描述中提到的“Mockingbird的v1变体”和“v2版本”的变化来看,Mockingbird正在经历持续的开发和改进过程。软件版本迭代是常见的开发实践,有助于修复已知问题,改善性能和用户体验,添加新功能等。 3. 服务器性能要求: 描述中强调了运行Mockingbird的服务器需要具备一定的性能,例如提及“WitherHosting的$ 1.25计划”,这暗示了反作弊系统对服务器资源的需求较高。这可能是因为反作弊机制需要频繁处理大量的数据和事件,以便及时检测和阻止作弊行为。 4. Waterdog问题: Waterdog是另一种Minecraft服务器软件,特别适合 PocketMine-MP。描述中提到如果将Mockingbird和Waterdog结合使用可能会遇到问题,这可能是因为两者在某些机制上的不兼容或Mockingbird对Waterdog的特定实现尚未完全优化。 5. GitHub使用及问题反馈: 作者鼓励用户通过GitHub问题跟踪系统来报告问题、旁路和功能建议。这是一个公共代码托管平台,广泛用于开源项目协作,便于开发者和用户进行沟通和问题管理。作者还提到请用户在GitHub上发布问题而不是在评论区留下不好的评论,这体现了良好的社区维护和用户交流的实践。 6. 软件标签: “pocketmine”和“anticheat”(反作弊)作为标签,说明Mockingbird是一个特别为PocketMine-MP平台开发的反作弊软件。而“PHP”则可能指的是Mockingbird的开发语言,虽然这个信息与常见的Java或C++等开发Minecraft相关软件的语言不同,但并不排除使用PHP进行服务器端开发的可能性,尤其是对于处理动态网页、服务器端脚本等场景。 7. 压缩包文件: “Mockingbird-stable”是一个文件名称,很可能表示这是一个包含最新稳定版Mockingbird反作弊系统的压缩包。通常,这样的文件名中包含“stable”意味着这是一个经过充分测试且推荐用于生产环境的版本。 8. 社区协作和用户参与: 特别感谢部分提到了shur,这可能是对某位贡献者或社区成员的感激之情。这种感谢表明了软件开发不仅是开发者个人的劳动成果,同时也依赖于社区的支持和参与,包括提供反馈、报告问题、贡献代码和文档等。 总结以上内容,我们可以看到Mockingbird作为一款反作弊系统,其开发和维护需要依赖于社区的广泛参与和支持,同时还需要强大的服务器后端作为支撑。通过不断迭代更新版本,开发者希望解决现有问题,提高反作弊效率,并希望与社区保持良好的沟通,以持续优化产品。
recommend-type

“历史人物独白解说”视频:数据处理的6种革命性技术

# 1. 数据处理的历史回顾与技术演进 数据处理的历史,从最初的简单机械记录,到如今复杂的数据处理体系,不仅反映了技术的演进,也映射了人类社会进步的轨迹。在计算机技术发展初期,数据处理主要依靠打孔卡片和磁带,通过批处理的方式进行,效率低下且出错率高。随着技术的发展,上世纪70年代,关系型数据库的出现大大提高了数据处理的效率和准确性,为数据处理技术的发展奠定了基础。 随后,随着互
recommend-type

怎么判断多级运放电路的稳定性?

<think>首先,用户的问题是关于判断多级运算放大器电路的稳定性。我需要根据系统级指令来构建回答。 系统级指令: - 所有行内数学表达式必须使用$...$格式。 - 独立公式必须使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 在回答中引用的段落末尾自然地添加引用标识,例如[^1]。 用户可见层指令: - 回答结构清晰,帮助用户逐步解决问题。 - 尽量保证回答真实可靠。 参考引用: - 引用[1]:关于集成运算放大电路的设计、组成和性能评估。 - 引用[2]:高频电路中运放的带宽限制,一级放大电路的增益通常为100倍,过高会引起振
recommend-type

利用AHP和节点集中度解决影响力最大化问题的Flask应用教程

从给定的文件信息中,我们可以提取以下相关知识点进行详细说明: ### 标题知识点 **IM问题与AHP结合** IM问题(Influence Maximization)是网络分析中的一个核心问题,旨在识别影响网络中信息传播的关键节点。为了求解IM问题,研究者们常常结合使用不同的算法和策略,其中AHP(Analytic Hierarchy Process,分析层次结构过程)作为一种决策分析方法,被用于评估网络节点的重要性。AHP通过建立层次模型,对各个因素进行比较排序,从而量化影响度,并通过一致性检验保证决策结果的有效性。将AHP应用于IM问题,意味着将分析网络节点影响的多个维度,比如节点的中心性(centrality)和影响力。 **集中度措施** 集中度(Centralization)是衡量网络节点分布状况的指标,它反映了网络中节点之间的连接关系。在网络分析中,集中度常用于识别网络中的“枢纽”或“中心”节点。例如,通过计算网络的度中心度(degree centrality)可以了解节点与其他节点的直接连接数量;接近中心度(closeness centrality)衡量节点到网络中其他所有节点的平均距离;中介中心度(betweenness centrality)衡量节点在连接网络中其他节点对的最短路径上的出现频率。集中度高意味着节点在网络中处于重要位置,对信息的流动和控制具有较大影响力。 ### 描述知识点 **Flask框架** Flask是一个轻量级的Web应用框架,它使用Python编程语言开发。它非常适合快速开发小型Web应用,以及作为微服务架构的一部分。Flask的一个核心特点是“微”,意味着它提供了基本的Web开发功能,同时保持了框架的小巧和灵活。Flask内置了开发服务器,支持Werkzeug WSGI工具包和Jinja2模板引擎,提供了RESTful请求分发和请求钩子等功能。 **应用布局** 一个典型的Flask应用会包含以下几个关键部分: - `app/`:这是应用的核心目录,包含了路由设置、视图函数、模型和控制器等代码文件。 - `static/`:存放静态文件,比如CSS样式表、JavaScript文件和图片等,这些文件的内容不会改变。 - `templates/`:存放HTML模板文件,Flask将使用这些模板渲染最终的HTML页面。模板语言通常是Jinja2。 - `wsgi.py`:WSGI(Web Server Gateway Interface)是Python应用程序和Web服务器之间的一种标准接口。这个文件通常用于部署到生产服务器时,作为应用的入口点。 **部署到Heroku** Heroku是一个支持多种编程语言的云平台即服务(PaaS),它允许开发者轻松部署、运行和管理应用。部署Flask应用到Heroku,需要几个步骤:首先,创建一个Procfile文件,告知Heroku如何启动应用;其次,确保应用的依赖关系被正确管理,通常通过一个requirements.txt文件列出所有依赖;最后,使用Git将应用推送到Heroku提供的仓库,Heroku会自动识别Procfile并开始部署过程。 ### 标签知识点 **HTML** HTML(HyperText Markup Language,超文本标记语言)是用于创建网页和Web应用的标准标记语言。它定义了网页的结构和内容。HTML文件由一系列元素组成,这些元素通过标签(tags)来表示,如`<p>`代表段落,`<a>`代表链接,`<img>`代表图像等。HTML5是当前使用的最新版本,支持更多的特性,如离线存储、多媒体和图形等。 ### 压缩包子文件的文件名称列表知识点 **IMproblem-using-AHP-and-centralisation-of-nodes-master** 这里的文件名称“IMproblem-using-AHP-and-centralisation-of-nodes-master”表明了一个GitHub仓库的名称,其中包含了源代码以及与项目相关的所有文件。从名称中可以看出,该仓库是关于如何结合AHP和节点集中度分析来解决IM问题的Flask应用程序。文件名中的“master”表明这是仓库的主分支(现在叫做main分支),它是项目最新的、可部署的代码版本。 综合来看,本段信息为我们提供了构建和部署一个使用Flask框架、针对IM问题使用AHP和节点集中度分析的Web应用的方法和步骤。同时,介绍了应用在不同环节中所需技术和组件的详细知识点。
recommend-type

视频内容自动生成算法:突破性的8大最新进展

# 1. 视频内容自动生成算法概述 ## 算法发展背景 随着人工智能技术的迅速发展,视频内容自动生成算法已经成为媒体和娱乐行业的重要工具。这些算法能够自动编辑和合成视频内容,使内容创作者能够以较低的成本和时间生成高质量的视频。从社交媒体动态到在线教育内容,视频内容自动生成的应用场景正在不断扩大。 ## 核心技术简述 视
recommend-type

Softmax函数实际应用举例

<think>嗯,用户现在想了解Softmax函数的实际应用场景,这和我之前详细解释过的Softmax原理是直接相关的。用户特意提到要参考站内引用,看来是想看到具体案例支撑。 从用户提问方式看,ta应该已经理解了Softmax的基本数学原理(毕竟我上次用向量[2,1,0.1]的例子详细演示过计算过程),现在需要落地到实践层面。这种从理论到应用的递进很合理,可能是个正在学习机器学习的学生,或者需要解决实际问题的工程师。 我注意到用户提供的引用里有些关键线索:引用[1]提到图像分类和概率解释,引用[4]强调指数放大的特性,引用[5]则对比了Sigmoid在多标签分类的应用差异。这些正好能支撑我
recommend-type

WDI项目1:PriceIsRight游戏开发实践

### 标题解析 标题“price-is-right:WDI项目1-PriceIsRight游戏”表明这是一个名为“Price Is Right”的游戏项目,这是WDI(Web Development Immersive,全栈网页开发沉浸式课程)的第一个项目。WDI是一种常用于IT培训机构的课程名称,旨在通过实战项目来培养学员的全栈网页开发能力。 ### 描述解析 描述中提到,该游戏的目的是为了练习基本的JavaScript技能。这表明游戏被设计成一个编程练习,让开发者通过实现游戏逻辑来加深对JavaScript的理解。描述中也提到了游戏是一个支持两个玩家的版本,包含了分配得分、跟踪得分以及宣布获胜者等逻辑,这是游戏开发中常见的功能实现。 开发者还提到使用了Bootstrap框架来增加网站的可伸缩性。Bootstrap是一个流行的前端框架,它让网页设计和开发工作更加高效,通过提供预设的CSS样式和JavaScript组件,让开发者能够快速创建出响应式的网站布局。此外,开发者还使用了HTML5和CSS进行网站设计,这表明项目也涉及到了前端开发的基础技能。 ### 标签解析 标签“JavaScript”指出了该游戏中核心编程语言的使用。JavaScript是一种高级编程语言,常用于网页开发中,负责实现网页上的动态效果和交互功能。通过使用JavaScript,开发者可以在不离开浏览器的情况下实现复杂的游戏逻辑和用户界面交互。 ### 文件名称解析 压缩包子文件的文件名称列表中仅提供了一个条目:“price-is-right-master”。这里的“master”可能指明了这是项目的主分支或者主版本,通常在版本控制系统(如Git)中使用。文件名中的“price-is-right”与标题相呼应,表明该文件夹内包含的代码和资源是与“Price Is Right”游戏相关的。 ### 知识点总结 #### 1. JavaScript基础 - **变量和数据类型**:用于存储得分等信息。 - **函数和方法**:用于实现游戏逻辑,如分配得分、更新分数。 - **控制结构**:如if-else语句和循环,用于实现游戏流程控制。 - **事件处理**:监听玩家的输入(如点击按钮)和游戏状态的变化。 #### 2. Bootstrap框架 - **网格系统**:实现响应式布局,让游戏界面在不同设备上都能良好展示。 - **预设组件**:可能包括按钮、表单、警告框等,用于快速开发用户界面。 - **定制样式**:根据需要自定义组件样式来符合游戏主题。 #### 3. HTML5与CSS - **语义化标签**:使用HTML5提供的新标签来构建页面结构,如`<header>`, `<section>`, `<footer>`等。 - **CSS布局**:使用Flexbox或Grid等布局技术对页面元素进行定位和排版。 - **样式设计**:通过CSS为游戏界面增添美观的视觉效果。 #### 4. 项目结构和版本控制 - **主分支管理**:`master`分支通常保存着项目的稳定版本,用于部署生产环境。 - **代码组织**:合理的文件结构有助于维护和扩展项目。 #### 5. 前端开发最佳实践 - **分离关注点**:将样式、脚本和内容分离,确保代码清晰易维护。 - **响应式设计**:确保游戏在多种设备和屏幕尺寸上均有良好的用户体验。 - **可访问性**:考虑键盘导航、屏幕阅读器等无障碍功能,让游戏更加友好。 #### 6. 交互式游戏开发 - **游戏逻辑实现**:创建一个简单的游戏循环,管理玩家输入和得分更新。 - **状态管理**:游戏中的得分和其他游戏状态需要妥善保存和更新。 - **用户界面反馈**:提供即时的视觉和听觉反馈,增强玩家体验。 通过上述知识点的解析,可以看出“Price Is Right”游戏项目不仅仅是一个简单的编程练习,它还融合了多种前端技术,包括JavaScript、Bootstrap、HTML5和CSS,以实现一个完整的、可交互的游戏体验。此项目也反映了开发者在掌握前端开发技能的同时,了解了如何组织代码、维护项目结构和实践开发最佳实践。