You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 00:05:35