--- tags: Python系統設計 --- # 图片转文字格式 X|Y|FFFFFF 可选取像素 ## image_pixel_picker ```python import tkinter as tk from tkinter import filedialog, messagebox from PIL import Image, ImageTk, ImageColor import os import csv from openpyxl import Workbook from tkinter import simpledialog # 初始化变量 selected_pixels = set() zoom_level = 1.0 # 默认的缩放级别 MIN_ZOOM = 1.0 MAX_ZOOM = 32.0 tk_img = None # 存放缩放后的图像 img = None image_path = "" pixel_size = 1 # 像素格子大小 show_rgb = True # 默认显示RGB selection_box = None # 用于存储框选区域的坐标 # 打开图片函数 def open_image(): global img, tk_img, image_path try: image_path = filedialog.askopenfilename(filetypes=[("Image files", "*.png;*.jpg;*.jpeg;*.bmp")]) if not image_path: return reset_selection(full=True) # 转为 RGB 后,再转为 BMP 格式避免内存错误 from io import BytesIO buffer = BytesIO() Image.open(image_path).convert('RGB').save(buffer, format='BMP') buffer.seek(0) img = Image.open(buffer) width, height = img.size display_width = int(width * pixel_size * zoom_level) display_height = int(height * pixel_size * zoom_level) canvas.config(scrollregion=(0, 0, display_width, display_height)) tk_img = ImageTk.PhotoImage(img.resize((display_width, display_height), Image.NEAREST)) canvas.create_image(0, 0, anchor='nw', image=tk_img) draw_grid(width, height) except Exception as e: messagebox.showerror("错误", f"无法打开图片:{e}") # 绘制网格函数 def draw_grid(width, height): for y in range(height): for x in range(width): canvas.create_rectangle( x * pixel_size * zoom_level, y * pixel_size * zoom_level, (x + 1) * pixel_size * zoom_level, (y + 1) * pixel_size * zoom_level, outline='gray' ) # 计算选取的最左上角 def get_origin(): """找到选取中最左上角的点""" if not selected_pixels: return None return min(selected_pixels, key=lambda p: (p[1], p[0])) # y优先 # 重绘选取 def redraw_selection(): global tk_img if img is None: return # 获取图像的原始宽度和高度 width, height = img.size # 计算缩放后的宽度和高度 display_width = int(width * pixel_size * zoom_level) display_height = int(height * pixel_size * zoom_level) # 清除 Canvas 上的所有内容(包括网格和图片) canvas.delete("all") # 缩放图像 zoomed_img = img.resize((display_width, display_height), Image.NEAREST) tk_img = ImageTk.PhotoImage(zoomed_img) # 设置新的滚动区域,确保图像的完整显示 canvas.config(scrollregion=(0, 0, display_width, display_height)) # 在 Canvas 上显示缩放后的图像 canvas.create_image(0, 0, anchor='nw', image=tk_img) # 绘制网格 draw_grid(width, height) # 获取最左上角的坐标 origin = get_origin() display_lines = [] # 按照Y排序,再按X排序 sorted_pixels = sorted(selected_pixels, key=lambda p: (p[1], p[0])) for x, y in sorted_pixels: rel_x = x - origin[0] rel_y = y - origin[1] r, g, b = img.getpixel((x, y)) color = f'{b:02X}{g:02X}{r:02X}' # 转换为HSV hsv = rgb_to_hsv(r, g, b) # 获取显示格式 if show_rgb: color_display = f"{b:02X}{g:02X}{r:02X}" else: color_display = f"{hsv[0]},{hsv[1]},{hsv[2]}" # 绘制红框 canvas.create_rectangle( x * pixel_size * zoom_level, y * pixel_size * zoom_level, (x + 1) * pixel_size * zoom_level, (y + 1) * pixel_size * zoom_level, outline='red', width=2 ) display_lines.append(f"{rel_x}|{rel_y}|{color_display}") # 确保 (0, 0) 坐标显示 if selected_pixels: text_output.delete("1.0", tk.END) text_output.insert(tk.END, "\n".join(display_lines)) # 处理点击事件 def on_click(event): if img is None: return x = event.x // pixel_size y = event.y // pixel_size coord = (x, y) if coord in selected_pixels: selected_pixels.remove(coord) # 取消选取 else: selected_pixels.add(coord) # 新增选取 redraw_selection() # RGB转HSV def rgb_to_hsv(r, g, b): r /= 255.0 g /= 255.0 b /= 255.0 mx = max(r, g, b) mn = min(r, g, b) h = (mx + mn) / 2 s = h v = mx return (h, s, v) # 切换显示RGB/HSV def toggle_rgb_hsv(): global show_rgb show_rgb = not show_rgb redraw_selection() # 选择保存格式并保存 def save_selection(): if not selected_pixels or not img or not image_path: return output_path = filedialog.asksaveasfilename(defaultextension=".txt", filetypes=[("Text files", "*.txt"), ("CSV files", "*.csv"), ("Excel files", "*.xlsx")]) if not output_path: return # 导出为txt if output_path.endswith(".txt"): save_as_txt(output_path) # 导出为CSV elif output_path.endswith(".csv"): save_as_csv(output_path) # 导出为Excel elif output_path.endswith(".xlsx"): save_as_excel(output_path) # 保存为TXT def save_as_txt(output_path): content = text_output.get("1.0", tk.END).strip() if not content: messagebox.showinfo("提示", "没有内容可以储存。") return with open(output_path, 'w', encoding='utf-8') as f: f.write(content) # 保存为CSV def save_as_csv(output_path): content = text_output.get("1.0", tk.END).strip() if not content: messagebox.showinfo("提示", "没有内容可以储存。") return rows = [line.split('|') for line in content.splitlines()] with open(output_path, 'w', newline='', encoding='utf-8') as f: writer = csv.writer(f) writer.writerow(["X", "Y", "Color"]) writer.writerows(rows) # 保存为Excel def save_as_excel(output_path): content = text_output.get("1.0", tk.END).strip() if not content: messagebox.showinfo("提示", "没有内容可以储存。") return wb = Workbook() ws = wb.active ws.append(["X", "Y", "Color"]) for line in content.splitlines(): parts = line.split('|') if len(parts) == 3: ws.append(parts) wb.save(output_path) # 重置选取 def reset_selection(full=False): """清除选取(不重新载入图档,除非 full=True)""" selected_pixels.clear() text_output.delete("1.0", tk.END) canvas.delete("all") if img and not full: width, height = img.size canvas.create_image(0, 0, anchor='nw', image=tk_img) draw_grid(width, height) # 框选功能的鼠标事件 def on_select_start(event): global selection_box # 使用 canvasx 和 canvasy 方法来获取放大后的鼠标位置,并考虑缩放比例 x = int(canvas.canvasx(event.x) // (pixel_size * zoom_level)) y = int(canvas.canvasy(event.y) // (pixel_size * zoom_level)) selection_box = [x, y] def on_select_end(event): global selection_box if selection_box: # 获取框选的起始和结束坐标,考虑缩放比例 x1, y1 = selection_box x2, y2 = int(canvas.canvasx(event.x) // (pixel_size * zoom_level)), int(canvas.canvasy(event.y) // (pixel_size * zoom_level)) # 如果是点击而非拖曳(小于一个格子),当作单点选择 if abs(x2 - x1) < 1 and abs(y2 - y1) < 1: coord = (x1, y1) if coord in selected_pixels: selected_pixels.remove(coord) # 取消选取 else: selected_pixels.add(coord) # 新增选取 else: # 框选逻辑 left, right = sorted([x1, x2]) top, bottom = sorted([y1, y2]) for x in range(left, right + 1): for y in range(top, bottom + 1): selected_pixels.add((x, y)) redraw_selection() selection_box = None MAX_PIXELS = 100000000 # 最大安全像素数,可根据测试调整 def zoom(factor): global zoom_level new_zoom = zoom_level * factor # 计算放大后是否超过最大像素数 if img: width, height = img.size expected_pixels = (width * new_zoom) * (height * new_zoom) if expected_pixels > MAX_PIXELS: messagebox.showwarning("警告", f"放大后图像像素数超过 {MAX_PIXELS:,},可能导致程序崩溃,已阻止操作。") return if MIN_ZOOM <= new_zoom <= MAX_ZOOM: old_x = canvas.canvasx(0) old_y = canvas.canvasy(0) w = canvas.winfo_width() h = canvas.winfo_height() center_x = old_x + w / 2 center_y = old_y + h / 2 zoom_level = new_zoom redraw_selection() new_center_x = center_x * factor new_center_y = center_y * factor canvas.xview_moveto((new_center_x - w / 2) / (img.width * pixel_size * zoom_level)) canvas.yview_moveto((new_center_y - h / 2) / (img.height * pixel_size * zoom_level)) # 放大功能 def zoom_in(): global zoom_level zoom_level = min(zoom_level * 1.2, MAX_ZOOM) # 最大放大5倍 redraw_selection() # 缩小功能 def zoom_out(): global zoom_level zoom_level = max(zoom_level / 1.2, MIN_ZOOM) # 最小缩小0.2倍 redraw_selection() # 创建主窗口 root = tk.Tk() root.title("图像像素选择器") # 建立可滚动的框架 canvas_frame = tk.Frame(root) canvas_frame.pack(fill="both", expand=True) # 水平与垂直滚动条 x_scroll = tk.Scrollbar(canvas_frame, orient="horizontal") x_scroll.pack(side="bottom", fill="x") y_scroll = tk.Scrollbar(canvas_frame, orient="vertical") y_scroll.pack(side="right", fill="y") # Canvas 本体 canvas = tk.Canvas(canvas_frame, xscrollcommand=x_scroll.set, yscrollcommand=y_scroll.set) canvas.pack(side="left", fill="both", expand=True) x_scroll.config(command=canvas.xview) y_scroll.config(command=canvas.yview) # 按钮区 btn_frame = tk.Frame(root) btn_frame.pack() tk.Button(btn_frame, text="打开图片", command=open_image).pack(side='left', padx=5) tk.Button(btn_frame, text="储存选择", command=save_selection).pack(side='left', padx=5) tk.Button(btn_frame, text="切换显示 RGB/HSV", command=toggle_rgb_hsv).pack(side='left', padx=5) tk.Button(btn_frame, text="清除选择", command=lambda: reset_selection(full=False)).pack(side='left', padx=5) tk.Button(btn_frame, text="放大 +", command=lambda: zoom(2)).pack(side='left', padx=5) tk.Button(btn_frame, text="缩小 -", command=lambda: zoom(0.5)).pack(side='left', padx=5) # 显示区域 text_output = tk.Text(root, height=10, width=50) text_output.pack(pady=5) # 绑定鼠标点击和框选事件 # canvas.bind("<Button-1>", on_click) canvas.bind("<ButtonPress-1>", on_select_start) canvas.bind("<ButtonRelease-1>", on_select_end) root.mainloop() ```