diff options
author | jaseg <s@jaseg.de> | 2013-09-16 15:33:01 +0200 |
---|---|---|
committer | jaseg <s@jaseg.de> | 2013-09-16 15:33:01 +0200 |
commit | 82cac0a5a211d573190253d3b40f89892bda713f (patch) | |
tree | ba67fa903d6b78c182df8b155e30ca46565e360b /pixelterm | |
parent | ed223febd651ea32bf784fb054ecb5082276cc6b (diff) | |
download | pixelterm-82cac0a5a211d573190253d3b40f89892bda713f.tar.gz pixelterm-82cac0a5a211d573190253d3b40f89892bda713f.tar.bz2 pixelterm-82cac0a5a211d573190253d3b40f89892bda713f.zip |
Fixed script installation
Diffstat (limited to 'pixelterm')
-rwxr-xr-x | pixelterm/colorcube.py | 25 | ||||
-rwxr-xr-x | pixelterm/gifterm.py | 53 | ||||
-rw-r--r-- | pixelterm/pixelterm.py | 109 | ||||
-rwxr-xr-x | pixelterm/pngmeta.py | 16 | ||||
-rwxr-xr-x | pixelterm/resolvecolor.py | 17 | ||||
-rw-r--r-- | pixelterm/unpixelterm.py | 122 | ||||
-rw-r--r-- | pixelterm/xtermcolors.py | 21 |
7 files changed, 363 insertions, 0 deletions
diff --git a/pixelterm/colorcube.py b/pixelterm/colorcube.py new file mode 100755 index 0000000..b7fe65c --- /dev/null +++ b/pixelterm/colorcube.py @@ -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() + diff --git a/pixelterm/gifterm.py b/pixelterm/gifterm.py new file mode 100755 index 0000000..252e49c --- /dev/null +++ b/pixelterm/gifterm.py @@ -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() + diff --git a/pixelterm/pixelterm.py b/pixelterm/pixelterm.py new file mode 100644 index 0000000..4239b98 --- /dev/null +++ b/pixelterm/pixelterm.py @@ -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() + diff --git a/pixelterm/pngmeta.py b/pixelterm/pngmeta.py new file mode 100755 index 0000000..5ddde98 --- /dev/null +++ b/pixelterm/pngmeta.py @@ -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() + diff --git a/pixelterm/resolvecolor.py b/pixelterm/resolvecolor.py new file mode 100755 index 0000000..83c1330 --- /dev/null +++ b/pixelterm/resolvecolor.py @@ -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() + diff --git a/pixelterm/unpixelterm.py b/pixelterm/unpixelterm.py new file mode 100644 index 0000000..8d1c120 --- /dev/null +++ b/pixelterm/unpixelterm.py @@ -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() + diff --git a/pixelterm/xtermcolors.py b/pixelterm/xtermcolors.py new file mode 100644 index 0000000..36cddc6 --- /dev/null +++ b/pixelterm/xtermcolors.py @@ -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] |