0x0 前言
老母亲要给小孙女的衣服上缝上图案,于是去百度搜了几张图,画质堪忧,格子也没有标序号,几乎不能看。
想着做个小工具,一来练练手,二来方便修改,导出图片后打印也清晰。
0x1 环境
# 安装pillow
$ python3 -m pip install pillow
# 安装pyqt6
$ python3 -m pip install pyside6
0x2 核心代码
Stitch
类主要实现了像素画棋盘格的大小、位置的计算,根据传入的鼠标位置获取对应的格子位置,以及保存和加载功能。
class Stitch():
def __init__(self, **kwargs) -> None:
self.line_normal = kwargs.get('line_normal', 1)
self.line_thick = kwargs.get('line_thick', 2)
self.thick_per_count = kwargs.get('thick_per_count', 5)
self.row = kwargs['row']
self.col = kwargs['col']
self.row_size = kwargs['row_size']
self.col_size = kwargs['col_size']
self.pad = kwargs['pad']
self.color_map = kwargs.get('color_map')
self.generate_cell_map()
self.update_board_size()
def update_board_size(self):
self.h = self.get_board_size(self.row, self.row_size, self.thick_per_count, self.line_normal, self.line_thick)
self.w = self.get_board_size(self.col, self.col_size, self.thick_per_count, self.line_normal, self.line_thick)
def save_json(self, file):
json_dict = {
"row": self.row,
"col": self.col,
"row_size": self.row_size,
"col_size": self.col_size,
"pad": self.pad,
"line_normal": self.line_normal,
"line_thick": self.line_thick,
"thick_per_count": self.thick_per_count,
"color_map": self.color_map
}
with open(file, 'w') as f:
json.dump(json_dict, f)
@classmethod
def default(cls):
json_dict = {
'line_normal': 1,
'line_thick': 1,
'thick_per_count': 5,
'row': 32,
'col': 32,
'row_size': 16,
'col_size': 16,
'pad': 32,
'color_map': {}
}
return cls(**json_dict)
@classmethod
def from_json(cls, file):
with open(file, 'r') as f:
json_dict = json.load(f)
return cls(**json_dict)
def generate_cell_map(self):
self.cell_map = {}
if self.color_map is None:
self.color_map = {}
for color, cells in self.color_map.items():
for row, col in cells:
cell_id = self.get_cell_id(row, col)
self.cell_map[cell_id] = color
def update_color_map(self, row, col, hex_color):
cell_id = self.get_cell_id(row, col)
if cell_id is None:
return
pt = [row, col]
prev_color = self.cell_map.get(cell_id, None)
if prev_color is not None:
if prev_color in self.color_map:
cmap = self.color_map[prev_color]
if pt in cmap:
cmap.remove(pt)
if hex_color is not None:
if hex_color in self.color_map:
self.color_map[hex_color].append(pt)
else:
self.color_map[hex_color] = [pt]
self.cell_map[cell_id] = hex_color
def get_board_lines(self):
rows, cols = [], []
x0, y0 = self.pad, self.pad
# draw rows
x1 = x0
x2 = x0 + self.w - 1
for i in range(self.row+1):
n = i + 1
is_thick = i % self.thick_per_count == 0 or i == self.row
y = self.get_line_pos(n, self.row_size, self.pad, self.thick_per_count, self.line_normal, self.line_thick)
rows.append([(x1, y), (x2, y), is_thick])
# draw cols
y1 = y0
y2 = y0 + self.h - 1
for i in range(self.col+1):
n = i + 1
is_thick = i % self.thick_per_count == 0 or i == self.col
x = self.get_line_pos(n, self.col_size, self.pad, self.thick_per_count, self.line_normal, self.line_thick)
cols.append([(x, y1), (x, y2), is_thick])
return rows, cols
def get_board_str(self):
rows, cols = [], []
row_font_size = self.row_size
col_font_size = self.col_size
x0, y0 = self.pad, self.pad
# draw row text
x1 = x0
x2 = x0 + self.w - 1
for i in range(self.row):
n = i + 1
if n % self.thick_per_count == 0 or i == self.row:
y = self.get_cell_pos(n, self.row_size, self.pad, self.thick_per_count, self.line_normal, self.line_thick)
y += (self.row_size + row_font_size) / 2
rows.append([x1-row_font_size-2, y, str(n)])
rows.append([x2+2, y, str(n)])
# draw col text
# draw cols
y1 = y0
y2 = y0 + self.h - 1
for i in range(self.col):
n = i + 1
if n % self.thick_per_count == 0 or i == self.col:
x = self.get_cell_pos(n, self.col_size, self.pad, self.thick_per_count, self.line_normal, self.line_thick)
x += (self.col_size - col_font_size) / 2
rows.append([x, y1-2, str(n)])
rows.append([x, y2+col_font_size+2, str(n)])
return rows, cols
def get_cell_id(self, row, col):
if row is None or col is None:
return None
cell_id = (row - 1) * self.row_size + col
return cell_id
def get_cell_qrect(self, row, col):
rect = self.get_cell_rect(row, col, self.row_size, self.col_size, self.pad, self.thick_per_count, self.line_normal, self.line_thick)
qrect = [rect[0], rect[1], rect[2]-rect[0]+1, rect[3]-rect[1]+1]
return qrect
def get_cell(self, x, y):
cell_row = self.get_cell_by_pos(y, self.row_size, self.h, self.row)
cell_col = self.get_cell_by_pos(x, self.col_size, self.w, self.col)
cell_id = None
cell_qrect = None
if cell_row is not None and cell_col is not None:
cell_qrect = self.get_cell_qrect(cell_row, cell_col)
cell_id = self.get_cell_id(cell_row, cell_col)
return cell_row, cell_col, cell_id, cell_qrect
def get_cell_by_pos(self, pos, size, boarder_size, cell_count):
pos -= self.pad
if pos < 0:
return None
if pos > self.pad + boarder_size:
return None
block_size = self.line_thick + (size + self.line_normal) * self.thick_per_count - self.line_normal
block_i = pos // block_size
block_bias = pos % block_size
line_bias = self.line_thick - self.line_normal
cell_size = self.line_normal + size
cell_n = (block_bias - line_bias) // cell_size + 1
cell = block_i * self.thick_per_count + cell_n
if cell < 1 or cell > cell_count:
cell = None
return cell
def save_image(self, file):
print(f'border size: {(self.w, self.h)}')
im_w = self.w + self.pad * 2
im_h = self.h + self.pad * 2
print(f'canvas size: {(im_w, im_h)}')
im = Image.new('RGB', size=(im_w, im_h), color='white')
draw = ImageDraw.Draw(im)
color_black = "#000000"
color_gray = "#cccccc"
x0, y0 = self.pad, self.pad
draw_later_lines = []
# draw rows
x1 = x0
x2 = x0 + self.w - 1
for i in range(self.row+1):
n = i + 1
y = self.get_line_pos(n, self.row_size, self.pad, self.thick_per_count, self.line_normal, self.line_thick)
is_thick = i % self.thick_per_count == 0 or i == self.row
t = self.line_thick if is_thick else self.line_normal
c = color_black if is_thick else color_gray
if is_thick:
draw_later_lines.append([[(x1, y), (x2, y)], c, t])
else:
draw.line([(x1, y), (x2, y)], c, t)
print(f'[row][{n}] pt1: {(x1, y)}, pt2: {(x2, y)}')
# draw cols
y1 = y0
y2 = y0 + self.h - 1
for i in range(self.col+1):
n = i + 1
x = self.get_line_pos(n, self.col_size, self.pad, self.thick_per_count, self.line_normal, self.line_thick)
is_thick = i % self.thick_per_count == 0 or i == self.col
t = self.line_thick if is_thick else self.line_normal
c = color_black if is_thick else color_gray
if is_thick:
draw_later_lines.append([[(x, y1), (x, y2)], c, t])
else:
draw.line([(x, y1), (x, y2)], c, t)
print(f'[col][{n}] pt1: {(x, y1)}, pt2: {(x, y1)}')
if len(draw_later_lines) > 0:
for i, l in enumerate(draw_later_lines):
n = i + 1
draw.line(l[0], l[1], l[2])
print(f'[thick][{n}] pt1: {l[0][0]}, pt2: {l[0][1]}')
# row_font = ImageFont.truetype("Ubuntu-M.ttf", self.row_size)
# col_font = ImageFont.truetype("Ubuntu-M.ttf", self.col_size)
row_font = ImageFont.truetype("simhei.ttf", self.row_size)
col_font = ImageFont.truetype("simhei.ttf", self.col_size)
# draw row text
for i in range(self.row):
n = i + 1
if n % self.thick_per_count == 0 or i == self.row:
y = self.get_cell_mid_pos(n, self.row_size, self.pad, self.thick_per_count, self.line_normal, self.line_thick)
draw.text((x1-2, y), str(n), anchor="rm", fill=color_black, font=row_font)
draw.text((x2+2, y), str(n), anchor="lm", fill=color_black, font=row_font)
# draw col text
for i in range(self.col):
n = i + 1
if n % self.thick_per_count == 0 or i == self.col:
x = self.get_cell_mid_pos(n, self.col_size, self.pad, self.thick_per_count, self.line_normal, self.line_thick)
draw.text((x, y1-2), str(n), anchor="md", fill=color_black, font=col_font)
draw.text((x, y2+2), str(n), anchor="ma", fill=color_black, font=col_font)
color_map = self.color_map
if color_map is not None:
for color, cells in color_map.items():
if not color.startswith('#'):
color = '#' + color
for cell in cells:
rect = self.get_cell_rect(cell[0], cell[1], self.row_size, self.col_size, self.pad, self.thick_per_count, self.line_normal, self.line_thick)
draw.rectangle(rect, fill=color, width=0)
im.save(file)
print(f'Saved image as "{file}"')
@classmethod
def convert_hex_to_rgb(self, hex):
hex = hex.strip('#').strip()
if len(hex) != 6:
return None
r = int(hex[0:2], 16)
g = int(hex[2:4], 16)
b = int(hex[4:6], 16)
return r, g, b
@classmethod
def convert_rgb_to_hex(self, r, g, b):
color = hex(r)[2:].zfill(2)
color += hex(g)[2:].zfill(2)
color += hex(b)[2:].zfill(2)
return color
@classmethod
def get_board_size(cls, n, size, thick_per_count, line_normal, line_thick):
return n * size + (n + 1) * line_normal + (math.ceil(n / thick_per_count) + 1) * (line_thick - line_normal)
@classmethod
def get_line_pos(cls, n, size, pad, thick_per_count, line_normal, line_thick):
i = n - 1
return pad + i * size + i * line_normal + math.ceil(i / thick_per_count) * (line_thick - line_normal)
@classmethod
def get_cell_pos(cls, n, size, pad, thick_per_count, line_normal, line_thick):
return pad + (n - 1) * size + n * line_normal + math.ceil(n / thick_per_count) * (line_thick - line_normal)
@classmethod
def get_cell_mid_pos(cls, n, size, pad, thick_per_count, line_normal, line_thick):
x = cls.get_cell_pos(n, size, pad, thick_per_count, line_normal, line_thick)
return x + math.floor(size / 2) - 1
@classmethod
def get_cell_rect(cls, row, col, row_size, col_size, pad, thick_per_count, line_normal, line_thick):
x1 = cls.get_cell_pos(col, col_size, pad, thick_per_count, line_normal, line_thick)
y1 = cls.get_cell_pos(row, row_size, pad, thick_per_count, line_normal, line_thick)
x2 = x1 + col_size - 1
y2 = y1 + row_size - 1
return (x1, y1, x2, y2)
0x4 如何运行
$ cd draw_stitch_gui
$ python3 -u draw_stitch_gui.py
0x5 效果图
0x6 完整源码
用鼠标点出像素画工具.7z
(61.64 KB, 下载次数: 51)
解压密码:52pojie.cn