From ac47d3d138bbabb8144c0cf44d4a906d248401b6 Mon Sep 17 00:00:00 2001 From: Joscha Date: Sat, 4 May 2019 16:48:01 +0000 Subject: [PATCH] Create initial visualize script --- visualize/visualize.py | 174 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 visualize/visualize.py diff --git a/visualize/visualize.py b/visualize/visualize.py new file mode 100644 index 0000000..cf89cf7 --- /dev/null +++ b/visualize/visualize.py @@ -0,0 +1,174 @@ +import argparse +import math +import re +import subprocess + +import png + + +class ArgumentException(Exception): + pass + +class Pixel: + GRAYSCALE = "grayscale" + COLOR = "color" + + def __init__(self, kind, value, alpha): + self._kind = kind + self._value = value + self._alpha = alpha + + @classmethod + def grayscale(cls, v, a=255): + return cls(cls.GRAYSCALE, v, a) + + @classmethod + def color(cls, r, g, b, a=255): + return cls(cls.COLOR, (r, g, b), a) + + def as_grayscale(self, alpha=False): + if self._kind == self.GRAYSCALE: + values = [self._value] + elif self._kind == self.COLOR: + values = [(self._value[0] + self._value[1] + self._value[2]) // 3] + + if alpha: + values.append(self._alpha) + + return values + + def as_rgb(self, alpha=False): + if self._kind == self.GRAYSCALE: + values = [self._value] * 3 + elif self._kind == self.COLOR: + values = list(self._value) + + if alpha: + values.append(self._alpha) + + return values + +Pixel.EMPTY = Pixel.grayscale(0, a=0) +Pixel.EMPTY_GREEN = Pixel.color(0, 255, 0) + +def parse_widthes(widthstr): + widthes = [] + + for width in widthstr.split(","): + match = re.fullmatch(r"((\d+)\*)?(\d+)", width) + if not match: + raise ArgumentException(f"invalid width: {width!r}") + + width_nr = int(match.group(3)) + + if match.group(2): + width_times = int(match.group(2)) + else: + width_times = 1 + + widthes.extend(width_times * [width_nr]) + + return widthes + +def optimal_width(total): + width = 1 + while total // width > width: + width += 1 + + return width + +def arrange_by_widthes(pixels, widthes): + rows = [] + + for width in widthes: + if pixels: + print(f"{len(pixels): 8} pixels left") + rows.append(pixels[:width]) + pixels = pixels[width:] + + + last_width = widthes[-1] + while pixels: + print(f"{len(pixels): 8} pixels left") + rows.append(pixels[:last_width]) + pixels = pixels[last_width:] + + return rows + +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: + if len(row) < width: + delta = width - len(row) + row.extend(delta * [pad_with]) + +def rows_as_grayscale(rows, alpha=True): + new_rows = [] + for row in rows: + values = (pixel.as_grayscale(alpha=alpha) for pixel in row) + new_rows.append(sum(values, [])) + return new_rows + +def rows_as_rgb(rows, alpha=True): + new_rows = [] + for row in rows: + values = (pixel.as_rgb(alpha=alpha) for pixel in row) + new_rows.append(sum(values, [])) + return new_rows + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("infile") + parser.add_argument("outfile") + parser.add_argument("--width", "-w") + parser.add_argument("--upscale", "-u", action="store_true") + parser.add_argument("--transparent", "-t", action="store_true") + args = parser.parse_args() + + # The following code should probably be put into separate methods 'n stuff, + # but it works for now and this is just an experimental script, so... I'll + # do it once I need to add more functionality. + + if args.width is not None: + widthes = parse_widthes(args.width) + else: + widthes = None # to be determined after file is loaded + + with open(args.infile, "rb") as f: + values = list(f.read()) + + print(f"{len(values)} bytes total") + + # TODO check for mode (grayscale vs rgb) + print("Loading pixels") + pixels = [Pixel.grayscale(value) for value in values] + + if widthes is None: + widthes = [optimal_width(len(pixels))] + + rows = arrange_by_widthes(pixels, widthes) + max_width = max_row_width(rows) + + if args.transparent: + print("Using transparent background") + alpha = True + pad_rows_to_width(rows, max_width, pad_with=Pixel.EMPTY) + else: + print("Using green background") + 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)) + + with open(args.outfile, "wb") as f: + writer.write(f, value_rows) + + if args.upscale: + print(f"Upscaling {args.outfile!r} with convert") + subprocess.run(["convert", args.outfile, "-filter", "point", "-resize", "1000%", args.outfile]) + +if __name__ == "__main__": + main()