Un générateur de multi-drawstat à partir d'une image. L'un des plus optimisés à ce jour.
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.

192 lines
5.0 KiB

  1. #!/usr/bin/env python3
  2. from argparse import ArgumentParser
  3. from bresenham import bresenham
  4. from PIL import Image, ImageDraw
  5. from random import randint
  6. import sys
  7. from time import time
  8. rand = lambda: randint(0,200)
  9. progress = False
  10. def print_stats(img):
  11. pixels = img.getdata()
  12. count = sum([1 for i in pixels if i == 0])
  13. if progress:
  14. print("{} black pixels over {} ({:.1%})".format(count, len(pixels), count/len(pixels)))
  15. def get_lines(img):
  16. lines = []
  17. pixels = [(x, y) for x in range(img.width) for y in range(img.height) if img.getpixel((x, y)) == 0]
  18. i, n = 0, len(pixels) * len(pixels)
  19. if progress:
  20. print("Get lines:", end = "")
  21. for a in pixels:
  22. for b in pixels:
  23. line = list(bresenham(a[0], a[1], b[0], b[1]))
  24. is_in = True
  25. for p in line:
  26. if p not in pixels:
  27. is_in = False
  28. break
  29. if is_in:
  30. lines.append(line)
  31. i += 1
  32. if progress:
  33. print("\rGet lines: {:.1%}".format(i / n), end = "")
  34. if progress:
  35. print("\rGet lines: complete")
  36. if progress:
  37. print("{} lines found".format(len(lines)))
  38. return lines
  39. def removing_doubles(lines):
  40. results = []
  41. i, n = 0, len(lines)
  42. if progress:
  43. print("Remove duplicated lines:", end = "")
  44. for l in lines:
  45. s = sorted(l)
  46. same = False
  47. for o in results:
  48. if sorted(o) == s:
  49. same = True
  50. break
  51. if same == False:
  52. results.append(l)
  53. i += 1
  54. if progress:
  55. print("\rRemove double lines: {:.0%}".format(i / n), end = "")
  56. if progress:
  57. print("\rRemove double lines: complete")
  58. if progress:
  59. print("{} lines kept".format(len(results)))
  60. return results
  61. def removing_useless(lines):
  62. results = []
  63. i, n = 0, len(lines)
  64. if progress:
  65. print("Remove useless lines:", end = "")
  66. for l in lines:
  67. inclusions = 0
  68. others = (x for x in lines if x != l)
  69. for k in others:
  70. if len(list(set(l).intersection(set(k)))) == len(l):
  71. inclusions += 1
  72. break
  73. if inclusions == 0 or len(l) == 1:
  74. results.append((len(l), l))
  75. i += 1
  76. if progress:
  77. print("\rRemove useless lines: {:.0%}".format(i / n), end = "")
  78. if progress:
  79. print("\rRemove useless lines: complete")
  80. if progress:
  81. print("{} lines kept".format(len(results)))
  82. return results
  83. def get_best_solution(img, lines):
  84. px_left = [(x, y) for x in range(img.width) for y in range(img.height) if img.getpixel((x, y)) == 0]
  85. results = []
  86. n = len(px_left)
  87. if progress:
  88. print("Draw:", end = "")
  89. while len(px_left):
  90. lines = [(sum([1 for p in l if p in px_left]) - len(l)/(2*max(img.size)), l) for n, l in lines]
  91. lines = sorted(lines)
  92. (p, line) = lines.pop()
  93. px_left = [p for p in px_left if p not in line]
  94. results.append((line[0], line[-1]))
  95. if progress:
  96. print("\rDraw: {:.0%}".format(1 - len(px_left)/n), end="")
  97. if progress:
  98. print("\rDraw: complete")
  99. if progress:
  100. print("Solution found in {} lines".format(len(results)))
  101. return results
  102. def generate_code(lines, args):
  103. str_x, str_y = "{", "{"
  104. for (xa, yb), (x, y) in lines:
  105. x, y = x + int(args.offset[0]), y + int(args.offset[1])
  106. a, b = xa - x + int(args.offset[0]), yb - y + int(args.offset[1])
  107. str_x += "{}{:+}{}, ".format(x, a, "T" if a else "")
  108. str_y += "{}{:+}{}, ".format(y, b, "T" if b else "")
  109. str_x = str_x[:-2] + "}"
  110. str_y = str_y[:-2] + "}"
  111. code = "Graph(X,Y)=({}, {})".format(str_x, str_y)
  112. return code
  113. if __name__ == "__main__":
  114. start = time()
  115. parser = ArgumentParser(description='Generate the Multi DrawStat code for an image.')
  116. parser.add_argument('path', type=str, help='path of the image to process')
  117. parser.add_argument('-d', '--draw', action='store_true', help='draw the result into a new file')
  118. parser.add_argument('-f', '--flip', action='store_true', help='flip image vertically (for inverted ViewWindow)')
  119. parser.add_argument('-i', '--info', action='store_true', help='print informative stats')
  120. parser.add_argument('-o', '--offset', nargs=2, default=(0, 0), help='offset for viewwindow. Default: (0, 0)')
  121. parser.add_argument('-p', '--progress', action='store_true', help='print progress info')
  122. parser.add_argument('-s', '--show', action='store_true', help='show the result')
  123. args = parser.parse_args()
  124. progress = args.progress
  125. try:
  126. image = Image.open(args.path)
  127. except:
  128. sys.exit("Error! Unable to open file.")
  129. try:
  130. image = image.convert('1')
  131. except:
  132. sys.exit("Error! Unable to convert to 1bit")
  133. if args.flip:
  134. image = image.transpose(Image.FLIP_TOP_BOTTOM)
  135. if args.info:
  136. print_stats(image)
  137. lines = get_lines(image)
  138. lines = removing_doubles(lines)
  139. lines = removing_useless(lines)
  140. lines = get_best_solution(image, lines)
  141. code = generate_code(lines, args)
  142. if args.draw or args.show:
  143. export = Image.new('RGB', image.size, 'white')
  144. drawer = ImageDraw.Draw(export)
  145. for ((a, b), (c, d)) in reversed(lines):
  146. drawer.line((a, b, c, d), fill=(rand(), rand(), rand()))
  147. export = export.resize((export.width * 8, export.height * 8))
  148. if args.draw:
  149. export.save(args.path[:-4] + "_gen.png")
  150. if args.show:
  151. export.show()
  152. if args.info:
  153. print("{} processed in {} lines ({:.3}s)".format(args.path, len(lines), time() - start))
  154. print(code)