276 lines
5.8 KiB
Python
Executable File
276 lines
5.8 KiB
Python
Executable File
#!/home/mongar/Escritorio/pruebas_oc/venv/bin/python3
|
|
# priforgepng
|
|
|
|
"""Forge PNG image from raw computation."""
|
|
|
|
from array import array
|
|
from fractions import Fraction
|
|
|
|
import argparse
|
|
import re
|
|
import sys
|
|
|
|
import png
|
|
|
|
|
|
def gen_glr(x):
|
|
"""Gradient Left to Right"""
|
|
return x
|
|
|
|
|
|
def gen_grl(x):
|
|
"""Gradient Right to Left"""
|
|
return 1 - x
|
|
|
|
|
|
def gen_gtb(x, y):
|
|
"""Gradient Top to Bottom"""
|
|
return y
|
|
|
|
|
|
def gen_gbt(x, y):
|
|
"""Gradient Bottom to Top"""
|
|
return 1.0 - y
|
|
|
|
|
|
def gen_rtl(x, y):
|
|
"""Radial gradient, centred at Top-Left"""
|
|
return max(1 - (float(x) ** 2 + float(y) ** 2) ** 0.5, 0.0)
|
|
|
|
|
|
def gen_rctr(x, y):
|
|
"""Radial gradient, centred at Centre"""
|
|
return gen_rtl(float(x) - 0.5, float(y) - 0.5)
|
|
|
|
|
|
def gen_rtr(x, y):
|
|
"""Radial gradient, centred at Top-Right"""
|
|
return gen_rtl(1.0 - float(x), y)
|
|
|
|
|
|
def gen_rbl(x, y):
|
|
"""Radial gradient, centred at Bottom-Left"""
|
|
return gen_rtl(x, 1.0 - float(y))
|
|
|
|
|
|
def gen_rbr(x, y):
|
|
"""Radial gradient, centred at Bottom-Right"""
|
|
return gen_rtl(1.0 - float(x), 1.0 - float(y))
|
|
|
|
|
|
def stripe(x, n):
|
|
return int(x * n) & 1
|
|
|
|
|
|
def gen_vs2(x):
|
|
"""2 Vertical Stripes"""
|
|
return stripe(x, 2)
|
|
|
|
|
|
def gen_vs4(x):
|
|
"""4 Vertical Stripes"""
|
|
return stripe(x, 4)
|
|
|
|
|
|
def gen_vs10(x):
|
|
"""10 Vertical Stripes"""
|
|
return stripe(x, 10)
|
|
|
|
|
|
def gen_hs2(x, y):
|
|
"""2 Horizontal Stripes"""
|
|
return stripe(float(y), 2)
|
|
|
|
|
|
def gen_hs4(x, y):
|
|
"""4 Horizontal Stripes"""
|
|
return stripe(float(y), 4)
|
|
|
|
|
|
def gen_hs10(x, y):
|
|
"""10 Horizontal Stripes"""
|
|
return stripe(float(y), 10)
|
|
|
|
|
|
def gen_slr(x, y):
|
|
"""10 diagonal stripes, rising from Left to Right"""
|
|
return stripe(x + y, 10)
|
|
|
|
|
|
def gen_srl(x, y):
|
|
"""10 diagonal stripes, rising from Right to Left"""
|
|
return stripe(1 + x - y, 10)
|
|
|
|
|
|
def checker(x, y, n):
|
|
return stripe(x, n) ^ stripe(y, n)
|
|
|
|
|
|
def gen_ck8(x, y):
|
|
"""8 by 8 checkerboard"""
|
|
return checker(x, y, 8)
|
|
|
|
|
|
def gen_ck15(x, y):
|
|
"""15 by 15 checkerboard"""
|
|
return checker(x, y, 15)
|
|
|
|
|
|
def gen_zero(x):
|
|
"""All zero (black)"""
|
|
return 0
|
|
|
|
|
|
def gen_one(x):
|
|
"""All one (white)"""
|
|
return 1
|
|
|
|
|
|
def yield_fun_rows(size, bitdepth, pattern):
|
|
"""
|
|
Create a single channel (monochrome) test pattern.
|
|
Yield each row in turn.
|
|
"""
|
|
|
|
width, height = size
|
|
|
|
maxval = 2 ** bitdepth - 1
|
|
if maxval > 255:
|
|
typecode = "H"
|
|
else:
|
|
typecode = "B"
|
|
pfun = pattern_function(pattern)
|
|
|
|
# The coordinates are an integer + 0.5,
|
|
# effectively sampling each pixel at its centre.
|
|
# This is morally better, and produces all 256 sample values
|
|
# in a 256-pixel wide gradient.
|
|
|
|
# We make a list of x coordinates here and re-use it,
|
|
# because Fraction instances are slow to allocate.
|
|
xs = [Fraction(x, 2 * width) for x in range(1, 2 * width, 2)]
|
|
|
|
# The general case is a function in x and y,
|
|
# but if the function only takes an x argument,
|
|
# it's handled in a special case that is a lot faster.
|
|
if n_args(pfun) == 2:
|
|
for y in range(height):
|
|
a = array(typecode)
|
|
fy = Fraction(Fraction(y + 0.5), height)
|
|
for fx in xs:
|
|
a.append(int(round(maxval * pfun(fx, fy))))
|
|
yield a
|
|
return
|
|
|
|
# For functions in x only, it's a _lot_ faster
|
|
# to generate a single row and repeatedly yield it
|
|
a = array(typecode)
|
|
for fx in xs:
|
|
a.append(int(round(maxval * pfun(x=fx))))
|
|
for y in range(height):
|
|
yield a
|
|
return
|
|
|
|
|
|
def generate(args):
|
|
"""
|
|
Create a PNG test image and write the file to stdout.
|
|
|
|
`args` should be an argparse Namespace instance or similar.
|
|
"""
|
|
|
|
size = args.size
|
|
bitdepth = args.depth
|
|
|
|
out = png.binary_stdout()
|
|
|
|
for pattern in args.pattern:
|
|
rows = yield_fun_rows(size, bitdepth, pattern)
|
|
writer = png.Writer(
|
|
size[0], size[1], bitdepth=bitdepth, greyscale=True, alpha=False
|
|
)
|
|
writer.write(out, rows)
|
|
|
|
|
|
def n_args(fun):
|
|
"""Number of arguments in fun's argument list."""
|
|
return fun.__code__.co_argcount
|
|
|
|
|
|
def pattern_function(pattern):
|
|
"""From `pattern`, a string,
|
|
return the function for that pattern.
|
|
"""
|
|
|
|
lpat = pattern.lower()
|
|
for name, fun in globals().items():
|
|
parts = name.split("_")
|
|
if parts[0] != "gen":
|
|
continue
|
|
if parts[1] == lpat:
|
|
return fun
|
|
|
|
|
|
def patterns():
|
|
"""
|
|
List the patterns.
|
|
"""
|
|
|
|
for name, fun in globals().items():
|
|
parts = name.split("_")
|
|
if parts[0] == "gen":
|
|
yield parts[1], fun.__doc__
|
|
|
|
|
|
def dimensions(s):
|
|
"""
|
|
Typecheck the --size option, which should be
|
|
one or two comma separated numbers.
|
|
Example: "64,40".
|
|
"""
|
|
|
|
tupl = re.findall(r"\d+", s)
|
|
if len(tupl) not in (1, 2):
|
|
raise ValueError("%r should be width or width,height" % s)
|
|
if len(tupl) == 1:
|
|
tupl *= 2
|
|
assert len(tupl) == 2
|
|
return list(map(int, tupl))
|
|
|
|
|
|
def main(argv=None):
|
|
if argv is None:
|
|
argv = sys.argv
|
|
parser = argparse.ArgumentParser(description="Forge greyscale PNG patterns")
|
|
|
|
parser.add_argument(
|
|
"-l", "--list", action="store_true", help="print list of patterns and exit"
|
|
)
|
|
parser.add_argument(
|
|
"-d", "--depth", default=8, type=int, metavar="N", help="N bits per pixel"
|
|
)
|
|
parser.add_argument(
|
|
"-s",
|
|
"--size",
|
|
default=[256, 256],
|
|
type=dimensions,
|
|
metavar="w[,h]",
|
|
help="width and height of the image in pixels",
|
|
)
|
|
parser.add_argument("pattern", nargs="*", help="name of pattern")
|
|
|
|
args = parser.parse_args(argv[1:])
|
|
|
|
if args.list:
|
|
for name, doc in sorted(patterns()):
|
|
print(name, doc, sep="\t")
|
|
return
|
|
|
|
if not args.pattern:
|
|
parser.error("--list or pattern is required")
|
|
return generate(args)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|