aboutsummaryrefslogtreecommitdiff
path: root/unpixelterm.py
blob: b9190b2f89f879023f95df7b67076abf687cef98 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
#!/usr/bin/env python

import os, sys, argparse, os.path
#NOTE: This script uses pygments for X256->RGB conversion since pygments is
#readily available. If you do not like pygments (e.g. because it is large),
#you could patch in something like https://github.com/magarcia/python-x256
#(but don't forget to send me a pull request ;)
from pygments.formatters import terminal256
from PIL import Image, PngImagePlugin
try:
	import re2 as re
except:
	import re

formatter = terminal256.Terminal256Formatter()
#HACK this adds two missing entries to pygment's color table
formatter.xterm_colors.append((0xe4, 0xe4, 0xe4))
formatter.xterm_colors.append((0xee, 0xee, 0xee))

def parse_escape_sequence(seq):
	#print('\\e'+seq[1:])
	codes = list(map(int, seq.lstrip('\x1b[').rstrip('m').split(';')))
	fg, bg = None, None
	i = 0
	while i<len(codes):
		if codes[i] in [38, 48]:
			if codes[i+1] == 5:
				c = formatter.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 = {}
	try:
		first = lines.index('$$$')
		second = lines[first+1:].index('$$$')
		comment = []
		d = {}
		for l in lines[first+1:second]:
			parts = l.split(': ')
			if len(parts) == 2:
				k,v = parts
				if k not in ['WIDTH', 'HEIGHT']:
					d[k.lower()] = d.get(k.lower(), []) + [v]
			elif l != '':
				comment.append(l)
		if comment:
			d['comment'] = d.get('comment', []) + comment
		metadata.update(d)
		lines[first:] = lines[first+1+second+1:]
	except:
		pass

	h = len(lines)*2
	w = max([ len(re.sub(r'\x1b\[[0-9;]+m|\$.*\$', '', line)) for line in lines ])
	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
					bw = int(re.match(r'\$balloon([0-9]*)\$', specialstr).group(1) or '1')
					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

if __name__ == '__main__':
	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())
		if args.verbose:
			print('Metadata:')
		pnginfo = PngImagePlugin.PngInfo()
		for k, v in metadata.items():
			if args.verbose:
				print('{:15}: {}'.format(k, '/'.join(v)))
			pnginfo.add_text(k, '/'.join(v))
		output = args.output or f.name.rstrip('.pony')+'.png'
		if args.output_dir:
			output = os.path.join(args.output_dir, os.path.basename(output))
		img.save(output, 'PNG', pnginfo=pnginfo)