commit 2064965072b780cb27da869c772d5113ca49eeaf Author: Cameron Date: Mon Nov 21 13:50:07 2022 -0600 last(?) commit from hilda diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..6d98f49 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +colorama==0.4.5 +Pillow==9.2.0 diff --git a/src/colors.py b/src/colors.py new file mode 100644 index 0000000..4dbe46e --- /dev/null +++ b/src/colors.py @@ -0,0 +1,114 @@ +from enum import Enum, auto, unique +from colorama import Fore, Back + +class Color: + def __init__(self, r, g, b): + self.R = r + self.G = g + self.B = b + + def __str__(self): + return str((self.R, self.G, self.B)) + + def __eq__(self, other): + return self.R == other.R and self.G == other.G and self.B == other.B + + def distance(self, other): + return ((self.R - other.R) ** 2 + + (self.G - other.G) ** 2 + + (self.B - other.B) ** 2) ** .5 + + def blend(self, other, weight): + oweight = 1 - weight + return Color(self.R * weight + other.R * oweight, + self.G * weight + other.G * oweight, + self.B * weight + other.B * oweight) + +#ubuntu +class ColorCode(Enum): + Black = Color(1, 1, 1) + Red = Color(222, 56, 43) + Green = Color(57, 181, 74) + Yellow = Color(255, 199, 6) + Blue = Color(0, 111, 184) + Magenta = Color(118, 38, 113) + Cyan = Color(44, 181, 233) + White = Color(204, 204, 204) + BrightBlack = Color(128, 128, 128) + BrightRed = Color(255, 0, 0) + BrightGreen = Color(0, 255, 0) + BrightYellow = Color(255, 255, 0) + BrightBlue = Color(0, 0, 255) + BrightMagenta = Color(255, 0, 255) + BrightCyan = Color(0, 255, 255) + BrightWhite = Color(255, 255, 255) + +def to_fore(color): + if color == ColorCode.Black: + return Fore.BLACK + elif color == ColorCode.Red: + return Fore.RED + elif color == ColorCode.Green: + return Fore.GREEN + elif color == ColorCode.Yellow: + return Fore.YELLOW + elif color == ColorCode.Blue: + return Fore.BLUE + elif color == ColorCode.Magenta: + return Fore.MAGENTA + elif color == ColorCode.Cyan: + return Fore.CYAN + elif color == ColorCode.White: + return Fore.WHITE + elif color == ColorCode.BrightBlack: + return Fore.LIGHTBLACK_EX + elif color == ColorCode.BrightRed: + return Fore.LIGHTRED_EX + elif color == ColorCode.BrightGreen: + return Fore.LIGHTGREEN_EX + elif color == ColorCode.BrightYellow: + return Fore.LIGHTYELLOW_EX + elif color == ColorCode.BrightBlue: + return Fore.LIGHTBLUE_EX + elif color == ColorCode.BrightMagenta: + return Fore.LIGHTMAGENTA_EX + elif color == ColorCode.BrightCyan: + return Fore.LIGHTCYAN_EX + elif color == ColorCode.BrightWhite: + return Fore.LIGHTWHITE_EX + raise ValueError(color) + +def to_back(color): + if color == ColorCode.Black: + return Back.BLACK + elif color == ColorCode.Red: + return Back.RED + elif color == ColorCode.Green: + return Back.GREEN + elif color == ColorCode.Yellow: + return Back.YELLOW + elif color == ColorCode.Blue: + return Back.BLUE + elif color == ColorCode.Magenta: + return Back.MAGENTA + elif color == ColorCode.Cyan: + return Back.CYAN + elif color == ColorCode.White: + return Back.WHITE + elif color == ColorCode.BrightBlack: + return Back.LIGHTBLACK_EX + elif color == ColorCode.BrightRed: + return Back.LIGHTRED_EX + elif color == ColorCode.BrightGreen: + return Back.LIGHTGREEN_EX + elif color == ColorCode.BrightYellow: + return Back.LIGHTYELLOW_EX + elif color == ColorCode.BrightBlue: + return Back.LIGHTBLUE_EX + elif color == ColorCode.BrightMagenta: + return Back.LIGHTMAGENTA_EX + elif color == ColorCode.BrightCyan: + return Back.LIGHTCYAN_EX + elif color == ColorCode.BrightWhite: + return Back.LIGHTWHITE_EX + raise ValueError(color) diff --git a/src/picshow.py b/src/picshow.py new file mode 100644 index 0000000..c2f8fc8 --- /dev/null +++ b/src/picshow.py @@ -0,0 +1,134 @@ +from PIL import Image +import argparse +import colorama +import sys +from io import BytesIO +from colors import Color, ColorCode, to_fore, to_back +from concurrent.futures import ProcessPoolExecutor + +THRESHOLD1 = 1.5 +THRESHOLD2 = 2 + +SHADE_FULL = u'█' +SHADE_LIGHT = u'░' +SHADE_MEDIUM = u'▒' +SHADE_DARK = u'▓' + +class Block: + def __init__(self, char, fore, back): + self.char = char + self.fore = fore + self.back = back + + def extract(self): + return self.char, to_fore(self.fore), to_back(self.back) + +def load_image(img): + if isinstance(img, Image.Image): + return img.convert('RGB') + if img: + return Image.open(img).convert('RGB') + else: + data = sys.stdin.buffer.read() + return Image.open(BytesIO(data)).convert('RGB') + +def split_image(img, rows, columns, col_index, row_index): + width = int(img.size[0] / columns) + height = int(img.size[1] / rows) + return img.crop((width * col_index, height * row_index, + width * (col_index+1), height * (row_index+1))) + +def to_block(img, simple=False): + red, green, blue = (0, 0, 0) + for pix in img.getdata(): + red += pix[0] + green += pix[1] + blue += pix[2] + res = img.size + size = res[0] * res[1] + color = Color(red / size, green / size, blue / size) + pairs = [(c, c.value.distance(color)) for c in ColorCode] + pairs.sort(key=lambda p : p[1]) + #cheap fix for divide by zero + ratio = pairs[1][1] / (pairs[0][1] or .00001) + if simple or ratio > THRESHOLD2: + char = SHADE_FULL + elif ratio > THRESHOLD1: + char = SHADE_DARK + else: + char = SHADE_MEDIUM + return Block(char, pairs[0][0], pairs[1][0]) + +def to_block2(img): + red, green, blue = (0, 0, 0) + for pix in img.getdata(): + red += pix[0] + green += pix[1] + blue += pix[2] + res = img.size + size = res[0] * res[1] + color = Color(red / size, green / size, blue / size) + match = min(block_list, key=lambda b: b[1].distance(color)) + return match[0] + +def print_blocks(rows): + for row in rows: + for block in row: + char, fore, back = block.extract() + print(fore + back + char + colorama.Fore.RESET + colorama.Back.RESET, end='') + print() + +def do_work(file, rows, columns, parallel = False) -> None: + with load_image(file) as im: + columns = columns or 15 + rows = rows or 10 + imgs = [] + for r in range(rows): + row_imgs = [] + for c in range(columns): + row_imgs.append(split_image(im, rows, columns, c, r)) + imgs.append(row_imgs) + if parallel: + pool = ProcessPoolExecutor() + map_func = pool.map + else: + map_func = map + blocks = map(lambda r: map_func(to_block2, r), imgs) + print_blocks(blocks) + +def main(vargs): + parser = argparse.ArgumentParser() + parser.add_argument('--simple', action='store_true', + help='only use full blocks') + parser.add_argument('-c', '--columns', type=int, + help='the width in chars', default=0) + parser.add_argument('-r', '--rows', type=int, + help='the height in chars', default=0) + parser.add_argument('-p', '--parallel', action='store_true', + help='process image in parallel') + parser.add_argument('file', nargs='?', + help='the file to show') + opts = parser.parse_args(vargs) + colorama.init() + return do_work(opts.file, opts.rows, opts.columns, opts.parallel) + +color_list = list(ColorCode) +block_list = [(Block(SHADE_FULL, c, ColorCode.White), + c.value) for c in color_list] +for i in range(len(color_list)): + for j in range(len(color_list)): + if i == j: + continue + #medium shades + if i < j: + block_list.append((Block(SHADE_MEDIUM, + color_list[i], color_list[j]), + color_list[i].value.blend(color_list[j].value, .5))) + #dark shade + block_list.append((Block(SHADE_DARK, + color_list[i], color_list[j]), + color_list[i].value.blend(color_list[j].value, .75))) + +if __name__ == '__main__': + exit(main(sys.argv[1:])) +