Python保存Tkinter Canvas的16x16图像为PNG时模糊问题求助
解决Tkinter Canvas保存16x16 PNG模糊问题
问题背景
尝试将Tkinter Canvas中绘制的16x16像素精灵保存为PNG文件时,两种常用方法都出现模糊现象,无法保留清晰的像素级细节。
原方法一(双Canvas)的问题
创建16x16的不可见Canvas同步绘制,再通过Postscript转换为PNG,但Postscript的渲染机制会引入缩放和抗锯齿,导致像素丢失,最终图像模糊。
原方法二(同步PIL Image)的问题
实时更新PIL Image时,ImageDraw.rectangle的半开区间绘制方式没有完全覆盖目标像素,导致边缘模糊或填充不完整。
修正方案:基于网格数据直接生成清晰PNG
不依赖Canvas的渲染结果,而是直接利用存储绘制数据的spriteGrid,在保存时逐像素构建16x16的PIL图像,确保完全精准的像素控制。
修正后的完整代码
import tkinter as tk from tkinter.colorchooser import askcolor import PIL.Image class DrawWindow(): def __init__(self): self.root = tk.Tk() self.colour = "black" self.menubar = tk.Menu(self.root) self.optionsmenu = tk.Menu(self.menubar, tearoff=0) self.optionsmenu.add_command(label="Save Clear Sprite", command=self.save_clear_png) self.menubar.add_cascade(menu=self.optionsmenu, label="Options") self.root.config(menu=self.menubar) self.sprFrame = tk.Frame(self.root) self.sprFrame.pack() self.spriteCanvas = tk.Canvas(self.sprFrame, height=288, width=288, bg="white") self.spriteCanvas.pack(pady=5, padx=5) self.pickColour = tk.Button(self.sprFrame, text="Pick colour", command=self.changeColour) self.pickColour.pack(pady=5) # 初始化16x16绘制网格 self.spriteGrid = [] for i in range(16): self.spriteGrid.append([]) for j in range(16): spot = Spot(i, j, 1, 16) self.spriteGrid[i].append(spot) self.spriteCanvas.bind("<Button-1>", self.get_clicked_pos) self.root.mainloop() def get_clicked_pos(self, event): y, x = event.y, event.x gap = 288 // 16 row = y // gap col = x // gap self.spriteGrid[row][col].tile = self.colour self.spriteDraw(gap) def changeColour(self): self.colour = askcolor(title="Sprite Colour")[1] def save_clear_png(self): # 创建16x16的RGB图像,白色背景 image = PIL.Image.new("RGB", (16, 16), "white") pixels = image.load() # 遍历网格,逐像素设置颜色 for row in range(16): for col in range(16): color = self.spriteGrid[row][col].tile if color: # 映射网格坐标到图像像素(row对应y轴,col对应x轴) pixels[col, row] = self.hex_to_rgb(color) image.save("clear_sprite.png") def hex_to_rgb(self, hex_color): # 将十六进制颜色转换为PIL支持的RGB元组 hex_color = hex_color.lstrip('#') return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4)) def spriteDraw(self, gap): # 清空Canvas避免图形重叠,添加灰色网格线方便编辑 self.spriteCanvas.delete("all") for i in self.spriteGrid: for j in i: if j.tile: self.spriteCanvas.create_rectangle( j.y * gap, j.x * gap, j.y * gap + gap, j.x * gap + gap, fill=j.tile, outline="gray" ) class Spot: def __init__(self, row, col, width, total_rows): self.row = row self.col = col self.x = row * width self.y = col * width self.tile = None self.width = width self.total_rows = total_rows DrawWindow()
核心优化说明
- 精准像素控制:保存时直接遍历
spriteGrid的颜色数据,为每个像素赋值,完全规避Canvas渲染或Postscript转换带来的精度损失。 - 颜色转换兼容:添加
hex_to_rgb方法,将Tkinter选择器返回的十六进制颜色正确转换为PIL所需的RGB格式。 - 编辑体验优化:重绘Canvas时先清空内容,添加灰色网格线,方便区分16x16的每个格子。
- 坐标映射修正:确保网格的行列与图像的像素坐标完全对应,避免错位。
内容的提问来源于stack exchange,提问作者37zak
相关产品推荐
相关产品推荐

