DeviLeo 发表于 2024-4-8 16:59

【Python】用鼠标点出像素画工具

## 0x0 前言

老母亲要给小孙女的衣服上缝上图案,于是去百度搜了几张图,画质堪忧,格子也没有标序号,几乎不能看。
想着做个小工具,一来练练手,二来方便修改,导出图片后打印也清晰。


## 0x1 环境

- python3.7+
- pillow
- pyqt6

``` shell
# 安装pillow
$ python3 -m pip install pillow

# 安装pyqt6
$ python3 -m pip install pyside6
```

## 0x2 核心代码
`Stitch`类主要实现了像素画棋盘格的大小、位置的计算,根据传入的鼠标位置获取对应的格子位置,以及保存和加载功能。

``` python
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 = 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 =
      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
                if pt in cmap:
                  cmap.remove(pt)
      
      if hex_color is not None:
            if hex_color in self.color_map:
                self.color_map.append(pt)
            else:
                self.color_map =
      
      self.cell_map = 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()
                rows.append()
      
      # 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()
                rows.append()
      
      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, rect-rect+1, rect-rect+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'[{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'[{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, l, l)
                print(f'[{n}] pt1: {l}, pt2: {l}')
               
      
      # 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, cell, 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, 16)
      g = int(hex, 16)
      b = int(hex, 16)
      return r, g, b
   
    @classmethod
    def convert_rgb_to_hex(self, r, g, b):
      color = hex(r).zfill(2)
      color += hex(g).zfill(2)
      color += hex(b).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 如何运行

``` shell
$ cd draw_stitch_gui
$ python3 -u draw_stitch_gui.py
```

## 0x5 效果图


## 0x6 完整源码


解压密码:52pojie.cn

wkdxz 发表于 2024-4-8 17:51

简单实用的软件,谢谢分享!

atoms 发表于 2024-4-8 19:11

楼主这个真不错 之前我知道一个在线画像素图像的 https://www.pixilart.com/draw虽然功能很强 但是是英文的

a5323671 发表于 2024-4-8 17:57

谢谢分享!

jiushiyaole 发表于 2024-4-8 17:58

很方便的工具{:1_893:}

qhkm99 发表于 2024-4-8 18:10

谢谢分享工具

1e3e 发表于 2024-4-8 18:43

谢谢分享呀

LightswornSnow 发表于 2024-4-8 19:16

标序号以前还真没考虑过,挺好的想法,参考一下

FHWX 发表于 2024-4-8 19:18

感谢分享!

yuzilin 发表于 2024-4-8 22:17

感谢分享呀~
页: [1] 2
查看完整版本: 【Python】用鼠标点出像素画工具