priforgepng 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. #!D:\TestPythonProject\TEST\venv\Scripts\python.exe
  2. # priforgepng
  3. """Forge PNG image from raw computation."""
  4. from array import array
  5. from fractions import Fraction
  6. import argparse
  7. import re
  8. import sys
  9. import png
  10. def gen_glr(x):
  11. """Gradient Left to Right"""
  12. return x
  13. def gen_grl(x):
  14. """Gradient Right to Left"""
  15. return 1 - x
  16. def gen_gtb(x, y):
  17. """Gradient Top to Bottom"""
  18. return y
  19. def gen_gbt(x, y):
  20. """Gradient Bottom to Top"""
  21. return 1.0 - y
  22. def gen_rtl(x, y):
  23. """Radial gradient, centred at Top-Left"""
  24. return max(1 - (float(x) ** 2 + float(y) ** 2) ** 0.5, 0.0)
  25. def gen_rctr(x, y):
  26. """Radial gradient, centred at Centre"""
  27. return gen_rtl(float(x) - 0.5, float(y) - 0.5)
  28. def gen_rtr(x, y):
  29. """Radial gradient, centred at Top-Right"""
  30. return gen_rtl(1.0 - float(x), y)
  31. def gen_rbl(x, y):
  32. """Radial gradient, centred at Bottom-Left"""
  33. return gen_rtl(x, 1.0 - float(y))
  34. def gen_rbr(x, y):
  35. """Radial gradient, centred at Bottom-Right"""
  36. return gen_rtl(1.0 - float(x), 1.0 - float(y))
  37. def stripe(x, n):
  38. return int(x * n) & 1
  39. def gen_vs2(x):
  40. """2 Vertical Stripes"""
  41. return stripe(x, 2)
  42. def gen_vs4(x):
  43. """4 Vertical Stripes"""
  44. return stripe(x, 4)
  45. def gen_vs10(x):
  46. """10 Vertical Stripes"""
  47. return stripe(x, 10)
  48. def gen_hs2(x, y):
  49. """2 Horizontal Stripes"""
  50. return stripe(float(y), 2)
  51. def gen_hs4(x, y):
  52. """4 Horizontal Stripes"""
  53. return stripe(float(y), 4)
  54. def gen_hs10(x, y):
  55. """10 Horizontal Stripes"""
  56. return stripe(float(y), 10)
  57. def gen_slr(x, y):
  58. """10 diagonal stripes, rising from Left to Right"""
  59. return stripe(x + y, 10)
  60. def gen_srl(x, y):
  61. """10 diagonal stripes, rising from Right to Left"""
  62. return stripe(1 + x - y, 10)
  63. def checker(x, y, n):
  64. return stripe(x, n) ^ stripe(y, n)
  65. def gen_ck8(x, y):
  66. """8 by 8 checkerboard"""
  67. return checker(x, y, 8)
  68. def gen_ck15(x, y):
  69. """15 by 15 checkerboard"""
  70. return checker(x, y, 15)
  71. def gen_zero(x):
  72. """All zero (black)"""
  73. return 0
  74. def gen_one(x):
  75. """All one (white)"""
  76. return 1
  77. def yield_fun_rows(size, bitdepth, pattern):
  78. """
  79. Create a single channel (monochrome) test pattern.
  80. Yield each row in turn.
  81. """
  82. width, height = size
  83. maxval = 2 ** bitdepth - 1
  84. if maxval > 255:
  85. typecode = "H"
  86. else:
  87. typecode = "B"
  88. pfun = pattern_function(pattern)
  89. # The coordinates are an integer + 0.5,
  90. # effectively sampling each pixel at its centre.
  91. # This is morally better, and produces all 256 sample values
  92. # in a 256-pixel wide gradient.
  93. # We make a list of x coordinates here and re-use it,
  94. # because Fraction instances are slow to allocate.
  95. xs = [Fraction(x, 2 * width) for x in range(1, 2 * width, 2)]
  96. # The general case is a function in x and y,
  97. # but if the function only takes an x argument,
  98. # it's handled in a special case that is a lot faster.
  99. if n_args(pfun) == 2:
  100. for y in range(height):
  101. a = array(typecode)
  102. fy = Fraction(Fraction(y + 0.5), height)
  103. for fx in xs:
  104. a.append(int(round(maxval * pfun(fx, fy))))
  105. yield a
  106. return
  107. # For functions in x only, it's a _lot_ faster
  108. # to generate a single row and repeatedly yield it
  109. a = array(typecode)
  110. for fx in xs:
  111. a.append(int(round(maxval * pfun(x=fx))))
  112. for y in range(height):
  113. yield a
  114. return
  115. def generate(args):
  116. """
  117. Create a PNG test image and write the file to stdout.
  118. `args` should be an argparse Namespace instance or similar.
  119. """
  120. size = args.size
  121. bitdepth = args.depth
  122. out = png.binary_stdout()
  123. for pattern in args.pattern:
  124. rows = yield_fun_rows(size, bitdepth, pattern)
  125. writer = png.Writer(
  126. size[0], size[1], bitdepth=bitdepth, greyscale=True, alpha=False
  127. )
  128. writer.write(out, rows)
  129. def n_args(fun):
  130. """Number of arguments in fun's argument list."""
  131. return fun.__code__.co_argcount
  132. def pattern_function(pattern):
  133. """From `pattern`, a string,
  134. return the function for that pattern.
  135. """
  136. lpat = pattern.lower()
  137. for name, fun in globals().items():
  138. parts = name.split("_")
  139. if parts[0] != "gen":
  140. continue
  141. if parts[1] == lpat:
  142. return fun
  143. def patterns():
  144. """
  145. List the patterns.
  146. """
  147. for name, fun in globals().items():
  148. parts = name.split("_")
  149. if parts[0] == "gen":
  150. yield parts[1], fun.__doc__
  151. def dimensions(s):
  152. """
  153. Typecheck the --size option, which should be
  154. one or two comma separated numbers.
  155. Example: "64,40".
  156. """
  157. tupl = re.findall(r"\d+", s)
  158. if len(tupl) not in (1, 2):
  159. raise ValueError("%r should be width or width,height" % s)
  160. if len(tupl) == 1:
  161. tupl *= 2
  162. assert len(tupl) == 2
  163. return list(map(int, tupl))
  164. def main(argv=None):
  165. if argv is None:
  166. argv = sys.argv
  167. parser = argparse.ArgumentParser(description="Forge greyscale PNG patterns")
  168. parser.add_argument(
  169. "-l", "--list", action="store_true", help="print list of patterns and exit"
  170. )
  171. parser.add_argument(
  172. "-d", "--depth", default=8, type=int, metavar="N", help="N bits per pixel"
  173. )
  174. parser.add_argument(
  175. "-s",
  176. "--size",
  177. default=[256, 256],
  178. type=dimensions,
  179. metavar="w[,h]",
  180. help="width and height of the image in pixels",
  181. )
  182. parser.add_argument("pattern", nargs="*", help="name of pattern")
  183. args = parser.parse_args(argv[1:])
  184. if args.list:
  185. for name, doc in sorted(patterns()):
  186. print(name, doc, sep="\t")
  187. return
  188. if not args.pattern:
  189. parser.error("--list or pattern is required")
  190. return generate(args)
  191. if __name__ == "__main__":
  192. main()