Fixed script installation
This commit is contained in:
parent
ed223febd6
commit
82cac0a5a2
11 changed files with 80 additions and 55 deletions
25
pixelterm/colorcube.py
Executable file
25
pixelterm/colorcube.py
Executable file
|
|
@ -0,0 +1,25 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Display an xterm-256color color palette on the terminal, including color ids
|
||||
|
||||
reset_sequence = '\033[39;49m'
|
||||
|
||||
def esc(i):
|
||||
return '\033[48;5;'+str(i)+'m'
|
||||
|
||||
def main():
|
||||
print(''.join([str(i).ljust(4) for i in range(16)]))
|
||||
print(' '.join([esc(i) for i in range(16)])+' ' + reset_sequence)
|
||||
|
||||
for j in range(6):
|
||||
for k in range(6):
|
||||
c = 16+j*6+k*6*6
|
||||
print(''.join([str(c+i).ljust(4) for i in range(6)]))
|
||||
print(' '.join([esc(c+i) for i in range(6)])+' ' + reset_sequence)
|
||||
|
||||
print(''.join([str(i).ljust(4) for i in range(16+6*6*6, 16+6*6*6+24)]))
|
||||
print(' '.join([esc(i) for i in range(16+6*6*6, 16+6*6*6+24)])+' ' + reset_sequence)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
53
pixelterm/gifterm.py
Executable file
53
pixelterm/gifterm.py
Executable file
|
|
@ -0,0 +1,53 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import os, sys, argparse, os.path, json, time, signal, atexit, pixelterm
|
||||
from PIL import Image, GifImagePlugin, ImageSequence
|
||||
|
||||
clear_screen = '\033[H\033[2J'
|
||||
cursor_invisible = '\033[?25l'
|
||||
cursor_visible = '\033[?25h'
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Render pixel images on 256-color ANSI terminals')
|
||||
parser.add_argument('image', type=str)
|
||||
parser.add_argument('-s', '--size', type=str, help='Terminal size, [W]x[H]')
|
||||
args = parser.parse_args()
|
||||
|
||||
tw, th = os.get_terminal_size()
|
||||
th = th*2
|
||||
if args.size:
|
||||
tw, th = map(int, args.size.split('x'))
|
||||
|
||||
img = Image.open(args.image)
|
||||
palette = img.getpalette()
|
||||
last_frame = Image.new("RGBA", img.size)
|
||||
frames = []
|
||||
|
||||
for frame in ImageSequence.Iterator(img):
|
||||
#This works around a known bug in Pillow
|
||||
#See also: http://stackoverflow.com/questions/4904940/python-converting-gif-frames-to-png
|
||||
frame.putpalette(palette)
|
||||
c = frame.convert("RGBA")
|
||||
|
||||
if img.info['background'] != img.info['transparency']:
|
||||
last_frame.paste(c, c)
|
||||
else:
|
||||
last_frame = c
|
||||
|
||||
im = last_frame.copy()
|
||||
im.thumbnail((tw, th), Image.NEAREST)
|
||||
frames.append(pixelterm.termify_pixels(im))
|
||||
|
||||
print(cursor_invisible)
|
||||
atexit.register(lambda:print(cursor_visible))
|
||||
signal.signal(signal.SIGTERM, lambda signum, stack_frame: exit(1))
|
||||
|
||||
while True:
|
||||
for frame in frames:
|
||||
print(clear_screen, pixelterm.reset_sequence)
|
||||
print(frame)
|
||||
time.sleep(img.info['duration']/1000.0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
109
pixelterm/pixelterm.py
Normal file
109
pixelterm/pixelterm.py
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import xtermcolors
|
||||
|
||||
reset_sequence = '\033[39;49m'
|
||||
|
||||
def termify_pixels(img):
|
||||
sx, sy = img.size
|
||||
out = ''
|
||||
|
||||
fg,bg = None,None
|
||||
fgd,bgd = {},{}
|
||||
def bgescape(color):
|
||||
nonlocal bg, bgd
|
||||
if bg == color:
|
||||
return ''
|
||||
bg=color
|
||||
if color == (0,0,0,0):
|
||||
return '\033[49m'
|
||||
if color in bgd:
|
||||
return bgd[color]
|
||||
r,g,b,_ = color
|
||||
bgd[color] = '\033[48;5;'+str(xtermcolors.closest_color(r,g,b))+'m'
|
||||
return bgd[color]
|
||||
|
||||
def fgescape(color):
|
||||
nonlocal fg, fgd
|
||||
if fg == color:
|
||||
return ''
|
||||
fg=color
|
||||
r,g,b,_ = color
|
||||
fgd[color] = '\033[38;5;'+str(xtermcolors.closest_color(r,g,b))+'m'
|
||||
return fgd[color]
|
||||
|
||||
def balloon(x,y):
|
||||
if x+1 == img.size[0] or img.im.getpixel((x+1, y)) != (0,255,0,127):
|
||||
w = 1
|
||||
while x-w >= 0 and img.im.getpixel((x-w, y)) == (0,255,0,127):
|
||||
w += 1
|
||||
return '$balloon{}$'.format(w)
|
||||
return ''
|
||||
|
||||
for y in range(0, sy, 2):
|
||||
for x in range(sx):
|
||||
coltop = img.im.getpixel((x, y))
|
||||
colbot = img.im.getpixel((x, y+1)) if y+1 < img.size[1] else (0,0,0,0)
|
||||
|
||||
if coltop[3] == 127: #Control colors
|
||||
out += reset_sequence
|
||||
out += {(255, 0, 0, 127): lambda x,y:'$\\$',
|
||||
(0, 0, 255, 127): lambda x,y:'$/$',
|
||||
(0, 255, 0, 127): balloon
|
||||
}.get(coltop, lambda x,y:' ')(x,y)
|
||||
continue
|
||||
|
||||
if coltop[3] != 255:
|
||||
coltop = (0,0,0,0)
|
||||
if colbot[3] != 255:
|
||||
colbot = (0,0,0,0)
|
||||
|
||||
#Da magicks: ▀█▄
|
||||
c,cf = '▀','█'
|
||||
te,be = fgescape,bgescape
|
||||
if coltop == (0,0,0,0) or ((coltop == bg or colbot == fg) and not colbot == (0,0,0,0)):
|
||||
c,cf,te,be = '▄',' ',be,te
|
||||
if colbot == coltop:
|
||||
c,te,be = cf,te,te
|
||||
out += te(coltop) + be(colbot) + c
|
||||
out = (out.rstrip() if bg == (0,0,0,0) else out) + '\n'
|
||||
return out[:-1] + reset_sequence + '\n'
|
||||
|
||||
def main():
|
||||
import os, sys, argparse, os.path, json
|
||||
from multiprocessing import Pool
|
||||
from PIL import Image, PngImagePlugin
|
||||
|
||||
parser = argparse.ArgumentParser(description='Render pixel images on 256-color ANSI terminals')
|
||||
parser.add_argument('image', type=str, nargs='*')
|
||||
parser.add_argument('-d', '--output-dir', type=str, help='Output directory (if not given, output to stdout)')
|
||||
args = parser.parse_args()
|
||||
|
||||
def convert(f):
|
||||
img = Image.open(f).convert("RGBA")
|
||||
if args.output_dir:
|
||||
print(f)
|
||||
foo, _, _ = f.rpartition('.png')
|
||||
output = os.path.join(args.output_dir, os.path.basename(foo)+'.pony')
|
||||
metadata = json.loads(img.info.get('pixelterm-metadata'))
|
||||
comment = metadata.get('_comment')
|
||||
if comment is not None:
|
||||
del metadata['_comment']
|
||||
comment = '\n'+comment
|
||||
else:
|
||||
comment = ''
|
||||
metadataarea = '$$$\n' +\
|
||||
'\n'.join([ '\n'.join([ k.upper() + ': ' + v for v in metadata[k] ]) for k in sorted(metadata.keys()) ]) +\
|
||||
comment + '\n$$$\n'
|
||||
with open(output, 'w') as of:
|
||||
of.write(metadataarea)
|
||||
of.write(termify_pixels(img))
|
||||
else:
|
||||
print(termify_pixels(img))
|
||||
|
||||
p = Pool()
|
||||
p.map(convert, args.image)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
16
pixelterm/pngmeta.py
Executable file
16
pixelterm/pngmeta.py
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import os, sys, argparse
|
||||
from PIL import Image, PngImagePlugin
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Print PNG metadata')
|
||||
parser.add_argument('image', type=str)
|
||||
args = parser.parse_args()
|
||||
img = Image.open(args.image)
|
||||
for k, v in img.info.items():
|
||||
print('{:15}: {}'.format(k, v))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
17
pixelterm/resolvecolor.py
Executable file
17
pixelterm/resolvecolor.py
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
def main():
|
||||
import os, sys, argparse, os.path, json, re
|
||||
import xtermcolors
|
||||
|
||||
# Resolve HTML-style hex RGB color codes to xterm-256color color numbers
|
||||
|
||||
if len(sys.argv) != 2:
|
||||
print('Usage: resolvecolor.py #RRGGBB')
|
||||
exit()
|
||||
|
||||
print(xtermcolors.closest_color(*[int(s, 16) for s in re.match('#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})', sys.argv[1]).groups()]))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
122
pixelterm/unpixelterm.py
Normal file
122
pixelterm/unpixelterm.py
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import os, sys, os.path
|
||||
from collections import defaultdict
|
||||
import xtermcolors
|
||||
from PIL import Image, PngImagePlugin
|
||||
try:
|
||||
import re2 as re
|
||||
except:
|
||||
import re
|
||||
|
||||
def parse_escape_sequence(seq):
|
||||
codes = list(map(int, seq[2:-1].split(';')))
|
||||
fg, bg = None, None
|
||||
i = 0
|
||||
while i<len(codes):
|
||||
if codes[i] in [38, 48]:
|
||||
if codes[i+1] == 5:
|
||||
c = xtermcolors.xterm_colors[codes[i+2]]
|
||||
fg, bg = (c, bg) if codes[i] == 38 else (fg, c)
|
||||
i += 2
|
||||
elif codes[i] == 39:
|
||||
fg = (0,0,0,0)
|
||||
elif codes[i] == 49:
|
||||
bg = (0,0,0,0)
|
||||
elif codes[i] == 0:
|
||||
fg, bg = (0,0,0,0), (0,0,0,0)
|
||||
i += 1
|
||||
return fg, bg
|
||||
|
||||
def unpixelterm(text):
|
||||
lines = text.split('\n')
|
||||
metadata = defaultdict(list)
|
||||
try:
|
||||
first = lines.index('$$$')
|
||||
second = lines[first+1:].index('$$$')
|
||||
metadataarea = lines[first+1:second+1]
|
||||
for i,l in enumerate(metadataarea):
|
||||
parts = l.split(': ')
|
||||
if len(parts) == 2:
|
||||
k,v = parts
|
||||
if k not in ['WIDTH', 'HEIGHT']:
|
||||
metadata[k.lower()] += [v]
|
||||
else:
|
||||
metadata['_comment'] = '\n'.join(metadataarea[i:])
|
||||
break
|
||||
lines[first:] = lines[first+1+second+1:]
|
||||
except:
|
||||
pass
|
||||
|
||||
if lines[-1] == '\x1b[0m':
|
||||
lines = lines[:-1]
|
||||
|
||||
h = len(lines)*2
|
||||
w = max([ len(re.sub(r'\x1b\[[0-9;]+m|\$balloon.*\$|\$', '', line)) for line in lines ])
|
||||
bw = int(re.search(r'\$balloon([0-9]*)\$', text).group(1) or '1')
|
||||
if bw > w: #Fuck special cases.
|
||||
w = bw
|
||||
img = Image.new('RGBA', (w, h))
|
||||
fg, bg = (0,0,0,0), (0,0,0,0)
|
||||
x, y = 0, 0
|
||||
for line in lines:
|
||||
for escapeseq, specialstr, char in re.findall(r'(\x1b\[[0-9;]+m)|(\$[^$]+\$)|(.)', line, re.DOTALL):
|
||||
if escapeseq:
|
||||
nfg, nbg = parse_escape_sequence(escapeseq)
|
||||
fg, bg = nfg or fg, nbg or bg
|
||||
elif specialstr:
|
||||
if specialstr == '$\\$':
|
||||
img.putpixel((x, y), (255, 0, 0, 127))
|
||||
img.putpixel((x, y+1), (255, 0, 0, 127))
|
||||
x += 1
|
||||
elif specialstr == '$/$':
|
||||
img.putpixel((x, y), (0, 0, 255, 127))
|
||||
img.putpixel((x, y+1), (0, 0, 255, 127))
|
||||
x += 1
|
||||
else: #(should be a) balloon
|
||||
for i in range(x, x+bw):
|
||||
img.putpixel((i, y), (0, 255, 0, 127))
|
||||
img.putpixel((i, y+1), (0, 255, 0, 127))
|
||||
x += bw
|
||||
elif char:
|
||||
#Da magicks: ▀█▄
|
||||
c = {' ': (bg, bg),
|
||||
'█': (fg, fg),
|
||||
'▀': (fg, bg),
|
||||
'▄': (bg, fg)}[char]
|
||||
img.putpixel((x, y), c[0])
|
||||
img.putpixel((x, y+1), c[1])
|
||||
x += 1
|
||||
x, y = 0, y+2
|
||||
return img, metadata
|
||||
|
||||
def main():
|
||||
import argparse, json
|
||||
|
||||
parser = argparse.ArgumentParser(description='Convert images rendered by pixelterm-like utilities back to PNG')
|
||||
parser.add_argument('-v', '--verbose', action='store_true')
|
||||
output_group = parser.add_mutually_exclusive_group()
|
||||
output_group.add_argument('-o', '--output', type=str, help='Output file name, defaults to ${input%.pony}.png')
|
||||
output_group.add_argument('-d', '--output-dir', type=str, help='Place output files here')
|
||||
parser.add_argument('input', type=argparse.FileType('r'), nargs='+')
|
||||
args = parser.parse_args()
|
||||
if len(args.input) > 1 and args.output:
|
||||
parser.print_help()
|
||||
print('You probably do not want to overwrite the given output file {} times.'.format(len(args.input)))
|
||||
sys.exit(1)
|
||||
|
||||
for f in args.input:
|
||||
if len(args.input) > 1:
|
||||
print(f.name)
|
||||
img, metadata = unpixelterm(f.read())
|
||||
pnginfo = PngImagePlugin.PngInfo()
|
||||
pnginfo.add_text('pixelterm-metadata', json.dumps(metadata))
|
||||
foo, _, _ = f.name.rpartition('.pony')
|
||||
output = args.output or foo+'.png'
|
||||
if args.output_dir:
|
||||
output = os.path.join(args.output_dir, os.path.basename(output))
|
||||
img.save(output, 'PNG', pnginfo=pnginfo)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
21
pixelterm/xtermcolors.py
Normal file
21
pixelterm/xtermcolors.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
|
||||
xterm_colors = []
|
||||
|
||||
# This is ripped out of pygments
|
||||
# I leave out the 16 standard colors since people tend to re-define them to their liking.
|
||||
|
||||
# colors 16..232: the 6x6x6 color cube
|
||||
_valuerange = (0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff)
|
||||
for i in range(217):
|
||||
r = _valuerange[(i // 36) % 6]
|
||||
g = _valuerange[(i // 6) % 6]
|
||||
b = _valuerange[i % 6]
|
||||
xterm_colors.append((r, g, b))
|
||||
|
||||
# colors 233..253: grayscale
|
||||
for i in range(1, 24):
|
||||
v = 8 + i * 10
|
||||
xterm_colors.append((v, v, v))
|
||||
|
||||
def closest_color(r, g, b):
|
||||
return 16+min([ ((r-rt)**2+(g-gt)**2+(b-bt)**2, i) for i,(rt,gt,bt) in enumerate(xterm_colors) ])[1]
|
||||
Loading…
Add table
Add a link
Reference in a new issue