last(?) commit from hilda
This commit is contained in:
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
colorama==0.4.5
|
||||||
|
Pillow==9.2.0
|
||||||
114
src/colors.py
Normal file
114
src/colors.py
Normal file
@@ -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)
|
||||||
134
src/picshow.py
Normal file
134
src/picshow.py
Normal file
@@ -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:]))
|
||||||
|
|
||||||
Reference in New Issue
Block a user