You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

stitchify.py 4.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. # coding: utf-8
  2. """ Converts pixel-art images into cross-stitch patterns.
  3. This tool assumes that 1px = 1 stitch.
  4. TODO:
  5. * Accept image name from command line. (DONE)
  6. * Change characters to symbols for ease of reading.
  7. * Expand number of symbols.
  8. * Create image from symbolized pixels instead of just printing to screen. (DONE)
  9. * Add grid lines and edge labels to image. (DONE)
  10. * Add legend to image, based on the `symbols` dictionary. (DONE)
  11. * Correspond hex colors to floss colors, where possible.
  12. * (Maybe) add stitch count for each color. (DONE)
  13. * (Maybe) add GUI. (DONE)
  14. * Make sure legend width doesn't exceed image width.
  15. """
  16. __author__ = "Noëlle Anthony"
  17. __version__ = "0.4.0"
  18. import sys
  19. from PIL import Image, ImageDraw
  20. from collections import defaultdict
  21. from gooey import Gooey, GooeyParser
  22. def create_stitch(img_name, img_out, display):
  23. img = Image.open(img_name)
  24. #oimg_name_bits = img_name.split(".")
  25. #oimg_name = "".join(oimg_name_bits[:-1]) + "_pattern." + oimg_name_bits[-1]
  26. oimg_name = img_out
  27. w,h = img.size
  28. symbols = defaultdict(str)
  29. symbols["transparent"] = " "
  30. characters = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz"
  31. symbol_counts = defaultdict(int)
  32. # l = 0
  33. lines = []
  34. for i in range(h):
  35. line = []
  36. # k = 0
  37. for j in range(w):
  38. c = "".join(["{}{}".format(hex(x//16).split('x')[-1], hex(x%16).split('x')[-1]) for x in list(img.getpixel((j,i)))])
  39. d = " "
  40. if c[-2:] == "ff":
  41. cs = c[:-2]
  42. if j == 0 and i == 0:
  43. symbols[cs] = " "
  44. elif cs not in symbols.keys():
  45. symbols[cs] = characters[0]
  46. characters = characters[1:]
  47. symbol_counts[cs] += 1
  48. d = symbols[cs]
  49. line.append(d)
  50. # print(d, end="")
  51. # k += 1
  52. # if k == 9:
  53. # print("|", end="")
  54. # k = 0
  55. lines.append(line)
  56. # print()
  57. # l += 1
  58. # if l == 9:
  59. # for ww in range(int(w*1.1)+1):
  60. # if (ww+1)%10 == 0:
  61. # print("+", end="")
  62. # else:
  63. # print("-", end="")
  64. # l = 0
  65. # print()
  66. # print("\nLEGEND")
  67. legend = []
  68. keys = 0
  69. for k,v in symbols.items():
  70. if v != " ":
  71. keys += 1
  72. legend.append("{}: #{} ({}ct)".format(v, k, symbol_counts[k]))
  73. print("{} keys".format(keys))
  74. # print("\n".join(legend))
  75. owid, ohgt = (w*10)+10, (h*10)+20+(15*(int(keys/3)+1))
  76. print((owid, ohgt))
  77. oimg = Image.new("RGB", (owid, ohgt), "white")
  78. draw = ImageDraw.Draw(oimg)
  79. woff, hoff = int(((w)%10)/2)+1, int(((h)%10)/2)+1
  80. for ww in range(1, w+1):
  81. posx = ww * 10
  82. linecolor = 0 if (posx-(woff*10)) % 100 == 0 else (128,128,128)
  83. linewidth = 2 if (posx-(woff*10)) % 100 == 0 else 1
  84. draw.line((posx, 10, posx, ((h+1)*10)), fill=linecolor, width=linewidth)
  85. for hh in range(1, h+2):
  86. posy = hh * 10
  87. linecolor = 0 if (posy-(hoff*10)) % 100 == 0 else (128,128,128)
  88. linewidth = 2 if (posy-(hoff*10)) % 100 == 0 else 1
  89. draw.line((10, posy, owid, posy), fill=linecolor, width=linewidth)
  90. char_positions = [x*10+4 for x in range(1,max(h,w)+1)]
  91. # print(char_positions)
  92. #char_colors = {" ": (0,0,0), "A": (0,0,0), "B": (128,0,0), "C": (0,128,0), "D": (0,255,255), "E": (128,128,0), "F": (128,0,128), "G": (0,0,0)}
  93. adjust = 0
  94. for line in lines:
  95. for char in range(len(line)):
  96. # print(char_positions[char])
  97. # print(line[char])
  98. try:
  99. draw.text((char_positions[char], char_positions[0]-4+adjust), line[char], fill=0)
  100. except:
  101. pass
  102. adjust += 10
  103. legend_out = ""
  104. item_ct = 0
  105. for item in legend:
  106. item_ct += 1
  107. legend_out += item
  108. if item_ct % 3 == 0:
  109. legend_out += "\n"
  110. else:
  111. legend_out += " "
  112. draw.text((20, (h*10)+20), legend_out, fill=0)
  113. oimg.save(oimg_name)
  114. print("Saved {}".format(oimg_name))
  115. if display:
  116. oimg.show()
  117. @Gooey
  118. def main():
  119. parser = GooeyParser()
  120. parser.add_argument("img_name", help="The image file you want to convert", widget="FileChooser")
  121. parser.add_argument("img_out", help="The name of the output pattern", widget="FileSaver")
  122. parser.add_argument("--display", help="Display the image after creation", action="store_true")
  123. args = parser.parse_args()
  124. create_stitch(args.img_name, args.img_out, args.display)
  125. if __name__ == "__main__":
  126. main()