#!/usr/bin/env python3
import sys
import getopt
from PIL import Image

xterm256colors = [ # http://pln.jonas.me/xterm-colors
	(0, (0x00, 0x00, 0x00)), # SYSTEM
	(1, (0x80, 0x00, 0x00)), # SYSTEM
	(2, (0x00, 0x80, 0x00)), # SYSTEM
	(3, (0x80, 0x80, 0x00)), # SYSTEM
	(4, (0x00, 0x00, 0x80)), # SYSTEM
	(5, (0x80, 0x00, 0x80)), # SYSTEM
	(6, (0x00, 0x80, 0x80)), # SYSTEM
	(7, (0xc0, 0xc0, 0xc0)), # SYSTEM
	(8, (0x80, 0x80, 0x80)), # SYSTEM
	(9, (0xff, 0x00, 0x00)), # SYSTEM
	(10, (0x00, 0xff, 0x00)), # SYSTEM
	(11, (0xff, 0xff, 0x00)), # SYSTEM
	(12, (0x00, 0x00, 0xff)), # SYSTEM
	(13, (0xff, 0x00, 0xff)), # SYSTEM
	(14, (0x00, 0xff, 0xff)), # SYSTEM
	(15, (0xff, 0xff, 0xff)), # SYSTEM
	(16, (0x00, 0x00, 0x00)), # Grey0
	(17, (0x00, 0x00, 0x5f)), # NavyBlue
	(18, (0x00, 0x00, 0x87)), # DarkBlue
	(19, (0x00, 0x00, 0xaf)), # Blue3
	(20, (0x00, 0x00, 0xd7)), # Blue3
	(21, (0x00, 0x00, 0xff)), # Blue1
	(22, (0x00, 0x5f, 0x00)), # DarkGreen
	(23, (0x00, 0x5f, 0x5f)), # DeepSkyBlue4
	(24, (0x00, 0x5f, 0x87)), # DeepSkyBlue4
	(25, (0x00, 0x5f, 0xaf)), # DeepSkyBlue4
	(26, (0x00, 0x5f, 0xd7)), # DodgerBlue3
	(27, (0x00, 0x5f, 0xff)), # DodgerBlue2
	(28, (0x00, 0x87, 0x00)), # Green4
	(29, (0x00, 0x87, 0x5f)), # SpringGreen4
	(30, (0x00, 0x87, 0x87)), # Turquoise4
	(31, (0x00, 0x87, 0xaf)), # DeepSkyBlue3
	(32, (0x00, 0x87, 0xd7)), # DeepSkyBlue3
	(33, (0x00, 0x87, 0xff)), # DodgerBlue1
	(34, (0x00, 0xaf, 0x00)), # Green3
	(35, (0x00, 0xaf, 0x5f)), # SpringGreen3
	(36, (0x00, 0xaf, 0x87)), # DarkCyan
	(37, (0x00, 0xaf, 0xaf)), # LightSeaGreen
	(38, (0x00, 0xaf, 0xd7)), # DeepSkyBlue2
	(39, (0x00, 0xaf, 0xff)), # DeepSkyBlue1
	(40, (0x00, 0xd7, 0x00)), # Green3
	(41, (0x00, 0xd7, 0x5f)), # SpringGreen3
	(42, (0x00, 0xd7, 0x87)), # SpringGreen2
	(43, (0x00, 0xd7, 0xaf)), # Cyan3
	(44, (0x00, 0xd7, 0xd7)), # DarkTurquoise
	(45, (0x00, 0xd7, 0xff)), # Turquoise2
	(46, (0x00, 0xff, 0x00)), # Green1
	(47, (0x00, 0xff, 0x5f)), # SpringGreen2
	(48, (0x00, 0xff, 0x87)), # SpringGreen1
	(49, (0x00, 0xff, 0xaf)), # MediumSpringGreen
	(50, (0x00, 0xff, 0xd7)), # Cyan2
	(51, (0x00, 0xff, 0xff)), # Cyan1
	(52, (0x5f, 0x00, 0x00)), # DarkRed
	(53, (0x5f, 0x00, 0x5f)), # DeepPink4
	(54, (0x5f, 0x00, 0x87)), # Purple4
	(55, (0x5f, 0x00, 0xaf)), # Purple4
	(56, (0x5f, 0x00, 0xd7)), # Purple3
	(57, (0x5f, 0x00, 0xff)), # BlueViolet
	(58, (0x5f, 0x5f, 0x00)), # Orange4
	(59, (0x5f, 0x5f, 0x5f)), # Grey37
	(60, (0x5f, 0x5f, 0x87)), # MediumPurple4
	(61, (0x5f, 0x5f, 0xaf)), # SlateBlue3
	(62, (0x5f, 0x5f, 0xd7)), # SlateBlue3
	(63, (0x5f, 0x5f, 0xff)), # RoyalBlue1
	(64, (0x5f, 0x87, 0x00)), # Chartreuse4
	(65, (0x5f, 0x87, 0x5f)), # DarkSeaGreen4
	(66, (0x5f, 0x87, 0x87)), # PaleTurquoise4
	(67, (0x5f, 0x87, 0xaf)), # SteelBlue
	(68, (0x5f, 0x87, 0xd7)), # SteelBlue3
	(69, (0x5f, 0x87, 0xff)), # CornflowerBlue
	(70, (0x5f, 0xaf, 0x00)), # Chartreuse3
	(71, (0x5f, 0xaf, 0x5f)), # DarkSeaGreen4
	(72, (0x5f, 0xaf, 0x87)), # CadetBlue
	(73, (0x5f, 0xaf, 0xaf)), # CadetBlue
	(74, (0x5f, 0xaf, 0xd7)), # SkyBlue3
	(75, (0x5f, 0xaf, 0xff)), # SteelBlue1
	(76, (0x5f, 0xd7, 0x00)), # Chartreuse3
	(77, (0x5f, 0xd7, 0x5f)), # PaleGreen3
	(78, (0x5f, 0xd7, 0x87)), # SeaGreen3
	(79, (0x5f, 0xd7, 0xaf)), # Aquamarine3
	(80, (0x5f, 0xd7, 0xd7)), # MediumTurquoise
	(81, (0x5f, 0xd7, 0xff)), # SteelBlue1
	(82, (0x5f, 0xff, 0x00)), # Chartreuse2
	(83, (0x5f, 0xff, 0x5f)), # SeaGreen2
	(84, (0x5f, 0xff, 0x87)), # SeaGreen1
	(85, (0x5f, 0xff, 0xaf)), # SeaGreen1
	(86, (0x5f, 0xff, 0xd7)), # Aquamarine1
	(87, (0x5f, 0xff, 0xff)), # DarkSlateGray2
	(88, (0x87, 0x00, 0x00)), # DarkRed
	(89, (0x87, 0x00, 0x5f)), # DeepPink4
	(90, (0x87, 0x00, 0x87)), # DarkMagenta
	(91, (0x87, 0x00, 0xaf)), # DarkMagenta
	(92, (0x87, 0x00, 0xd7)), # DarkViolet
	(93, (0x87, 0x00, 0xff)), # Purple
	(94, (0x87, 0x5f, 0x00)), # Orange4
	(95, (0x87, 0x5f, 0x5f)), # LightPink4
	(96, (0x87, 0x5f, 0x87)), # Plum4
	(97, (0x87, 0x5f, 0xaf)), # MediumPurple3
	(98, (0x87, 0x5f, 0xd7)), # MediumPurple3
	(99, (0x87, 0x5f, 0xff)), # SlateBlue1
	(100, (0x87, 0x87, 0x00)), # Yellow4
	(101, (0x87, 0x87, 0x5f)), # Wheat4
	(102, (0x87, 0x87, 0x87)), # Grey53
	(103, (0x87, 0x87, 0xaf)), # LightSlateGrey
	(104, (0x87, 0x87, 0xd7)), # MediumPurple
	(105, (0x87, 0x87, 0xff)), # LightSlateBlue
	(106, (0x87, 0xaf, 0x00)), # Yellow4
	(107, (0x87, 0xaf, 0x5f)), # DarkOliveGreen3
	(108, (0x87, 0xaf, 0x87)), # DarkSeaGreen
	(109, (0x87, 0xaf, 0xaf)), # LightSkyBlue3
	(110, (0x87, 0xaf, 0xd7)), # LightSkyBlue3
	(111, (0x87, 0xaf, 0xff)), # SkyBlue2
	(112, (0x87, 0xd7, 0x00)), # Chartreuse2
	(113, (0x87, 0xd7, 0x5f)), # DarkOliveGreen3
	(114, (0x87, 0xd7, 0x87)), # PaleGreen3
	(115, (0x87, 0xd7, 0xaf)), # DarkSeaGreen3
	(116, (0x87, 0xd7, 0xd7)), # DarkSlateGray3
	(117, (0x87, 0xd7, 0xff)), # SkyBlue1
	(118, (0x87, 0xff, 0x00)), # Chartreuse1
	(119, (0x87, 0xff, 0x5f)), # LightGreen
	(120, (0x87, 0xff, 0x87)), # LightGreen
	(121, (0x87, 0xff, 0xaf)), # PaleGreen1
	(122, (0x87, 0xff, 0xd7)), # Aquamarine1
	(123, (0x87, 0xff, 0xff)), # DarkSlateGray1
	(124, (0xaf, 0x00, 0x00)), # Red3
	(125, (0xaf, 0x00, 0x5f)), # DeepPink4
	(126, (0xaf, 0x00, 0x87)), # MediumVioletRed
	(127, (0xaf, 0x00, 0xaf)), # Magenta3
	(128, (0xaf, 0x00, 0xd7)), # DarkViolet
	(129, (0xaf, 0x00, 0xff)), # Purple
	(130, (0xaf, 0x5f, 0x00)), # DarkOrange3
	(131, (0xaf, 0x5f, 0x5f)), # IndianRed
	(132, (0xaf, 0x5f, 0x87)), # HotPink3
	(133, (0xaf, 0x5f, 0xaf)), # MediumOrchid3
	(134, (0xaf, 0x5f, 0xd7)), # MediumOrchid
	(135, (0xaf, 0x5f, 0xff)), # MediumPurple2
	(136, (0xaf, 0x87, 0x00)), # DarkGoldenrod
	(137, (0xaf, 0x87, 0x5f)), # LightSalmon3
	(138, (0xaf, 0x87, 0x87)), # RosyBrown
	(139, (0xaf, 0x87, 0xaf)), # Grey63
	(140, (0xaf, 0x87, 0xd7)), # MediumPurple2
	(141, (0xaf, 0x87, 0xff)), # MediumPurple1
	(142, (0xaf, 0xaf, 0x00)), # Gold3
	(143, (0xaf, 0xaf, 0x5f)), # DarkKhaki
	(144, (0xaf, 0xaf, 0x87)), # NavajoWhite3
	(145, (0xaf, 0xaf, 0xaf)), # Grey69
	(146, (0xaf, 0xaf, 0xd7)), # LightSteelBlue3
	(147, (0xaf, 0xaf, 0xff)), # LightSteelBlue
	(148, (0xaf, 0xd7, 0x00)), # Yellow3
	(149, (0xaf, 0xd7, 0x5f)), # DarkOliveGreen3
	(150, (0xaf, 0xd7, 0x87)), # DarkSeaGreen3
	(151, (0xaf, 0xd7, 0xaf)), # DarkSeaGreen2
	(152, (0xaf, 0xd7, 0xd7)), # LightCyan3
	(153, (0xaf, 0xd7, 0xff)), # LightSkyBlue1
	(154, (0xaf, 0xff, 0x00)), # GreenYellow
	(155, (0xaf, 0xff, 0x5f)), # DarkOliveGreen2
	(156, (0xaf, 0xff, 0x87)), # PaleGreen1
	(157, (0xaf, 0xff, 0xaf)), # DarkSeaGreen2
	(158, (0xaf, 0xff, 0xd7)), # DarkSeaGreen1
	(159, (0xaf, 0xff, 0xff)), # PaleTurquoise1
	(160, (0xd7, 0x00, 0x00)), # Red3
	(161, (0xd7, 0x00, 0x5f)), # DeepPink3
	(162, (0xd7, 0x00, 0x87)), # DeepPink3
	(163, (0xd7, 0x00, 0xaf)), # Magenta3
	(164, (0xd7, 0x00, 0xd7)), # Magenta3
	(165, (0xd7, 0x00, 0xff)), # Magenta2
	(166, (0xd7, 0x5f, 0x00)), # DarkOrange3
	(167, (0xd7, 0x5f, 0x5f)), # IndianRed
	(168, (0xd7, 0x5f, 0x87)), # HotPink3
	(169, (0xd7, 0x5f, 0xaf)), # HotPink2
	(170, (0xd7, 0x5f, 0xd7)), # Orchid
	(171, (0xd7, 0x5f, 0xff)), # MediumOrchid1
	(172, (0xd7, 0x87, 0x00)), # Orange3
	(173, (0xd7, 0x87, 0x5f)), # LightSalmon3
	(174, (0xd7, 0x87, 0x87)), # LightPink3
	(175, (0xd7, 0x87, 0xaf)), # Pink3
	(176, (0xd7, 0x87, 0xd7)), # Plum3
	(177, (0xd7, 0x87, 0xff)), # Violet
	(178, (0xd7, 0xaf, 0x00)), # Gold3
	(179, (0xd7, 0xaf, 0x5f)), # LightGoldenrod3
	(180, (0xd7, 0xaf, 0x87)), # Tan
	(181, (0xd7, 0xaf, 0xaf)), # MistyRose3
	(182, (0xd7, 0xaf, 0xd7)), # Thistle3
	(183, (0xd7, 0xaf, 0xff)), # Plum2
	(184, (0xd7, 0xd7, 0x00)), # Yellow3
	(185, (0xd7, 0xd7, 0x5f)), # Khaki3
	(186, (0xd7, 0xd7, 0x87)), # LightGoldenrod2
	(187, (0xd7, 0xd7, 0xaf)), # LightYellow3
	(188, (0xd7, 0xd7, 0xd7)), # Grey84
	(189, (0xd7, 0xd7, 0xff)), # LightSteelBlue1
	(190, (0xd7, 0xff, 0x00)), # Yellow2
	(191, (0xd7, 0xff, 0x5f)), # DarkOliveGreen1
	(192, (0xd7, 0xff, 0x87)), # DarkOliveGreen1
	(193, (0xd7, 0xff, 0xaf)), # DarkSeaGreen1
	(194, (0xd7, 0xff, 0xd7)), # Honeydew2
	(195, (0xd7, 0xff, 0xff)), # LightCyan1
	(196, (0xff, 0x00, 0x00)), # Red1
	(197, (0xff, 0x00, 0x5f)), # DeepPink2
	(198, (0xff, 0x00, 0x87)), # DeepPink1
	(199, (0xff, 0x00, 0xaf)), # DeepPink1
	(200, (0xff, 0x00, 0xd7)), # Magenta2
	(201, (0xff, 0x00, 0xff)), # Magenta1
	(202, (0xff, 0x5f, 0x00)), # OrangeRed1
	(203, (0xff, 0x5f, 0x5f)), # IndianRed1
	(204, (0xff, 0x5f, 0x87)), # IndianRed1
	(205, (0xff, 0x5f, 0xaf)), # HotPink
	(206, (0xff, 0x5f, 0xd7)), # HotPink
	(207, (0xff, 0x5f, 0xff)), # MediumOrchid1
	(208, (0xff, 0x87, 0x00)), # DarkOrange
	(209, (0xff, 0x87, 0x5f)), # Salmon1
	(210, (0xff, 0x87, 0x87)), # LightCoral
	(211, (0xff, 0x87, 0xaf)), # PaleVioletRed1
	(212, (0xff, 0x87, 0xd7)), # Orchid2
	(213, (0xff, 0x87, 0xff)), # Orchid1
	(214, (0xff, 0xaf, 0x00)), # Orange1
	(215, (0xff, 0xaf, 0x5f)), # SandyBrown
	(216, (0xff, 0xaf, 0x87)), # LightSalmon1
	(217, (0xff, 0xaf, 0xaf)), # LightPink1
	(218, (0xff, 0xaf, 0xd7)), # Pink1
	(219, (0xff, 0xaf, 0xff)), # Plum1
	(220, (0xff, 0xd7, 0x00)), # Gold1
	(221, (0xff, 0xd7, 0x5f)), # LightGoldenrod2
	(222, (0xff, 0xd7, 0x87)), # LightGoldenrod2
	(223, (0xff, 0xd7, 0xaf)), # NavajoWhite1
	(224, (0xff, 0xd7, 0xd7)), # MistyRose1
	(225, (0xff, 0xd7, 0xff)), # Thistle1
	(226, (0xff, 0xff, 0x00)), # Yellow1
	(227, (0xff, 0xff, 0x5f)), # LightGoldenrod1
	(228, (0xff, 0xff, 0x87)), # Khaki1
	(229, (0xff, 0xff, 0xaf)), # Wheat1
	(230, (0xff, 0xff, 0xd7)), # Cornsilk1
	(231, (0xff, 0xff, 0xff)), # Grey100
	(232, (0x08, 0x08, 0x08)), # Grey3
	(233, (0x12, 0x12, 0x12)), # Grey7
	(234, (0x1c, 0x1c, 0x1c)), # Grey11
	(235, (0x26, 0x26, 0x26)), # Grey15
	(236, (0x30, 0x30, 0x30)), # Grey19
	(237, (0x3a, 0x3a, 0x3a)), # Grey23
	(238, (0x44, 0x44, 0x44)), # Grey27
	(239, (0x4e, 0x4e, 0x4e)), # Grey30
	(240, (0x58, 0x58, 0x58)), # Grey35
	(241, (0x62, 0x62, 0x62)), # Grey39
	(242, (0x6c, 0x6c, 0x6c)), # Grey42
	(243, (0x76, 0x76, 0x76)), # Grey46
	(244, (0x80, 0x80, 0x80)), # Grey50
	(245, (0x8a, 0x8a, 0x8a)), # Grey54
	(246, (0x94, 0x94, 0x94)), # Grey58
	(247, (0x9e, 0x9e, 0x9e)), # Grey62
	(248, (0xa8, 0xa8, 0xa8)), # Grey66
	(249, (0xb2, 0xb2, 0xb2)), # Grey70
	(250, (0xbc, 0xbc, 0xbc)), # Grey74
	(251, (0xc6, 0xc6, 0xc6)), # Grey78
	(252, (0xd0, 0xd0, 0xd0)), # Grey82
	(253, (0xda, 0xda, 0xda)), # Grey85
	(254, (0xe4, 0xe4, 0xe4)), # Grey89
	(255, (0xee, 0xee, 0xee)), # Grey93
]

class StdoutWrapper():
	def write(f, b):
		sys.stdout.write(b.decode('utf-8'))
	def flush(f):
		sys.stdout.flush()
	def close(f):
		return

def colordiff(ca, cb):
	return (
		abs(ca[0] - cb[0]) +
		abs(ca[1] - cb[1]) +
		abs(ca[2] - cb[2])
	)

def find_colorcode(c):
	winning_colordiff = 999
	winning_colorcode = -1
	for cc in xterm256colors:
		cdiff = colordiff(c, cc[1])
		if cdiff < winning_colordiff:
			winning_colordiff = cdiff
			winning_colorcode = cc[0]
	return winning_colorcode

def ctrl_fg(cc):
	if cc == -1:
		return b"\x1b[39m"
	else:
		return b"\x1b[38;5;" + str(cc).encode("ascii") + b"m"

def ctrl_bg(cc):
	if cc == -1:
		return b"\x1b[49m"
	else:
		return b"\x1b[48;5;" + str(cc).encode("ascii") + b"m"

try:
	opts, args = getopt.getopt(sys.argv[1:], "o:ua", ["unicode-compat"])
except getopt.GetoptError as e:
	print(str(e))
	exit(1)

if len(args) < 1:
	print("Usage: %s [-o FILENAME] [-u] [--unicode-compat] [-a] FILENAME" % sys.argv[0])
	print("Converts images into color sequences (xterm-256color)")
	print("\t-o FILENAME\t\tWrite color sequences to file instead of outputting to stdout")
	print("\t-u\t\t\tUse unicode characters (U+2584 and U+2580) to allow 2x resolution on the same space")
	print("\t--unicode-compat\tTry to be more compatible to terminals that are not xterm")
	print("\t\t\t\tUse this if it doesn't look like right")
	print("\t-a\t\t\tUse alpha channel of image")
	exit(1)

if len(args) > 1:
	print("Too many arguments.")
	exit(1)

o_alpha = ('-a', '') in opts
o_unicode = ('-u', '') in opts
o_unicode_compat = ('--unicode-compat', '') in opts
o_outfile = ""

for e in opts:
	if e[0] == "-o":
		o_outfile = e[1]

if o_unicode_compat and not o_unicode:
	print("Warning: --unicode-compat without -u is no-op")

img = Image.open(args[0])
if o_unicode:
	warnlimit = 240 * 72
else:
	warnlimit = 120 * 36
if img.size[0] * img.size[1] > warnlimit:
	print("Warning: Big image, did you forget to scale it down?")
img = img.convert("RGBA" if o_alpha else "RGB")
imgd = img.load()
if o_outfile == "":
	tf = StdoutWrapper()
else:
	tf = open(o_outfile, "wb")

if not o_unicode:
	for y in range(img.size[1]):
		lastcc = -1
		for x in range(img.size[0]):
			cc = -1 if o_alpha and imgd[x, y][3] < 128 else find_colorcode(imgd[x, y])
			if cc == lastcc:
				tf.write(b"  ")
			else:
				tf.write(ctrl_bg(cc) + b"  ") # 2 spaces per pixel because chars are non-square
				lastcc = cc
		tf.write(b"\x1b[0m\n")
		tf.flush()
else:
	for y in range(0, img.size[1], 2):
		lastfg, lastbg = -1, -1
		for x in range(img.size[0]):
			if y == img.size[1] - 1:
				ccbg = -1
				ccfg = -1 if o_alpha and imgd[x, y][3] < 128 else find_colorcode(imgd[x, y])
			else:
				ccbg = -1 if o_alpha and imgd[x, y][3] < 128 else find_colorcode(imgd[x, y])
				ccfg = -1 if o_alpha and imgd[x, y+1][3] < 128 else find_colorcode(imgd[x, y+1])

			u = False
			if ccfg == -1 and ccbg != -1 and not o_unicode_compat:
				# This is needed to make transparency work when there are 2 pixels
				#  like this:
				#  [ NON-TRANSPARENT ]
				#  [   TRANSPARENT   ]
				# XXX: also causes ugly lines in xfce4-terminal
				ccfg, ccbg = ccbg, ccfg
				u = True
			elif ccfg == lastbg and ccbg == lastfg and not o_unicode_compat:
				# Optimize stuff by using UPPER HALF BLOCK and leaving colors alone
				#  instead of swapping colors and using default char
				# XXX: Guess what? yep, causes glitches in xfce4-terminal
				ccfg, ccbg = ccbg, ccfg
				u = True

			if ccfg != lastfg:
				tf.write(ctrl_fg(ccfg))
				lastfg = ccfg
			if ccbg != lastbg:
				tf.write(ctrl_bg(ccbg))
				lastbg = ccbg

			if ccfg == ccbg:
				tf.write(b" ")
			elif u:
				tf.write("\u2580".encode('utf-8'))
			elif y == img.size[1] - 1 and not o_unicode_compat:
				# The last pixel row of an uneven image is encoded as UPPER HALF BLOCK
				#  with the default bg color and the pixel color as the fg color
				# XXX: causes ugly lines in xfce4-terminal
				tf.write("\u2580".encode('utf-8'))
			else:
				tf.write("\u2584".encode('utf-8'))
		tf.write(b"\x1b[0m\n")
		tf.flush()

tf.close()
