Clean up and speed up

This commit is contained in:
Joscha 2019-05-04 17:52:16 +00:00
parent ac47d3d138
commit 04fe68a9eb

View file

@ -9,10 +9,14 @@ import png
class ArgumentException(Exception): class ArgumentException(Exception):
pass pass
class Pixel: class Pixel:
GRAYSCALE = "grayscale" GRAYSCALE = "grayscale"
COLOR = "color" COLOR = "color"
_cache = {}
def __init__(self, kind, value, alpha): def __init__(self, kind, value, alpha):
self._kind = kind self._kind = kind
self._value = value self._value = value
@ -20,11 +24,25 @@ class Pixel:
@classmethod @classmethod
def grayscale(cls, v, a=255): def grayscale(cls, v, a=255):
return cls(cls.GRAYSCALE, v, a) params = (cls.GRAYSCALE, v, a)
pixel = cls._cache.get(params)
if pixel is None:
pixel = cls(cls.GRAYSCALE, v, a)
cls._cache[params] = pixel
return pixel
@classmethod @classmethod
def color(cls, r, g, b, a=255): def color(cls, r, g, b, a=255):
return cls(cls.COLOR, (r, g, b), a) params = (cls.COLOR, r, g, b, a)
pixel = cls._cache.get(params)
if pixel is None:
pixel = cls(cls.COLOR, (r, g, b), a)
cls._cache[params] = pixel
return pixel
def as_grayscale(self, alpha=False): def as_grayscale(self, alpha=False):
if self._kind == self.GRAYSCALE: if self._kind == self.GRAYSCALE:
@ -51,6 +69,8 @@ class Pixel:
Pixel.EMPTY = Pixel.grayscale(0, a=0) Pixel.EMPTY = Pixel.grayscale(0, a=0)
Pixel.EMPTY_GREEN = Pixel.color(0, 255, 0) Pixel.EMPTY_GREEN = Pixel.color(0, 255, 0)
def parse_widthes(widthstr): def parse_widthes(widthstr):
widthes = [] widthes = []
@ -77,46 +97,50 @@ def optimal_width(total):
return width return width
def load_pixels(bytestr):
for byte in bytestr:
yield Pixel.grayscale(byte)
def arrange_by_widthes(pixels, widthes): def arrange_by_widthes(pixels, widthes):
rows = [] index = 0
pixel_amount = len(pixels)
for width in widthes: for width in widthes:
if pixels: if index < pixel_amount:
print(f"{len(pixels): 8} pixels left") #print(f"At pixel {index: 8}")
rows.append(pixels[:width]) yield pixels[index:index+width]
pixels = pixels[width:] index += width
last_width = widthes[-1] last_width = widthes[-1]
while pixels: while index < pixel_amount:
print(f"{len(pixels): 8} pixels left") #print(f"At pixel {index: 8}")
rows.append(pixels[:last_width]) yield pixels[index:index+last_width]
pixels = pixels[last_width:] index += last_width
return rows def pad_rows_to_width(rows, width, pad_with=Pixel.EMPTY):
def max_row_width(rows):
return max(map(len, rows))
def pad_rows_to_width(rows, width, pad_with=Pixel.EMPTY): # modifies in-place
for row in rows: for row in rows:
if len(row) < width: if len(row) < width:
delta = width - len(row) delta = width - len(row)
row.extend(delta * [pad_with]) yield row + delta * [pad_with]
else:
yield row
def rows_as_grayscale(rows, alpha=True): def rows_as_grayscale(rows, alpha=True):
new_rows = []
for row in rows: for row in rows:
values = (pixel.as_grayscale(alpha=alpha) for pixel in row) combined = []
new_rows.append(sum(values, [])) for pixel in row:
return new_rows combined.extend(pixel.as_grayscale(alpha=alpha))
yield combined
def rows_as_rgb(rows, alpha=True): def rows_as_rgb(rows, alpha=True):
new_rows = []
for row in rows: for row in rows:
values = (pixel.as_rgb(alpha=alpha) for pixel in row) combined = []
new_rows.append(sum(values, [])) for pixel in row:
return new_rows combined.extend(pixel.as_rgb(alpha=alpha))
yield combined
def main(): def main():
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
@ -131,44 +155,59 @@ def main():
# but it works for now and this is just an experimental script, so... I'll # but it works for now and this is just an experimental script, so... I'll
# do it once I need to add more functionality. # do it once I need to add more functionality.
if args.transparent:
alpha = True
greyscale = True
pad_with = Pixel.EMPTY
else:
alpha = False
greyscale = False
pad_with = Pixel.EMPTY_GREEN
if args.width is not None: if args.width is not None:
widthes = parse_widthes(args.width) widthes = parse_widthes(args.width)
else: else:
widthes = None # to be determined after file is loaded widthes = None # determined once we know the amount of pixels
print(f"Reading from {args.infile!r}")
with open(args.infile, "rb") as f: with open(args.infile, "rb") as f:
values = list(f.read()) bytestr = f.read()
print(f"{len(bytestr)} bytes")
print(f"{len(values)} bytes total") pixels = list(load_pixels(bytestr))
print(f"{len(pixels)} pixels")
# TODO check for mode (grayscale vs rgb)
print("Loading pixels")
pixels = [Pixel.grayscale(value) for value in values]
if widthes is None: if widthes is None:
widthes = [optimal_width(len(pixels))] widthes = [optimal_width(len(pixels))]
rows = arrange_by_widthes(pixels, widthes) print("Arranging pixels into rows")
max_width = max_row_width(rows) rows = list(arrange_by_widthes(pixels, widthes))
max_width = max(map(len, rows))
height = len(rows)
print(f"{height} rows, maximum width is {max_width}")
print("Padding rows to the right width")
padded_rows = list(pad_rows_to_width(rows, max_width, pad_with))
if args.transparent: print("Converting rows to raw pixel values")
print("Using transparent background") if greyscale:
alpha = True value_rows = rows_as_grayscale(padded_rows, alpha=alpha)
pad_rows_to_width(rows, max_width, pad_with=Pixel.EMPTY)
else: else:
print("Using green background") value_rows = rows_as_rgb(padded_rows, alpha=alpha)
alpha = False
pad_rows_to_width(rows, max_width, pad_with=Pixel.EMPTY_GREEN)
value_rows = rows_as_rgb(rows, alpha=alpha)
writer = png.Writer(alpha=alpha, width=max_width, height=len(rows))
print(f"Writing to {args.outfile!r}")
writer = png.Writer(greyscale=greyscale, alpha=alpha,
width=max_width, height=height)
with open(args.outfile, "wb") as f: with open(args.outfile, "wb") as f:
writer.write(f, value_rows) writer.write(f, value_rows)
if args.upscale: if args.upscale:
print(f"Upscaling {args.outfile!r} with convert") print(f"Upscaling {args.outfile!r} with convert")
subprocess.run(["convert", args.outfile, "-filter", "point", "-resize", "1000%", args.outfile]) subprocess.run([
"convert", args.outfile,
"-filter", "point",
"-resize", "1000%",
args.outfile
])
if __name__ == "__main__": if __name__ == "__main__":
main() main()