---
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()
```