Skip to content

Instantly share code, notes, and snippets.

@sfan5
Last active July 13, 2020 18:24
Show Gist options
  • Star 19 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save sfan5/2d971a5a087881a966c3 to your computer and use it in GitHub Desktop.
Save sfan5/2d971a5a087881a966c3 to your computer and use it in GitHub Desktop.
Converts images into color sequences (xterm-256color)
#!/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()
@mathiasbynens
Copy link

If anyone uses that script: make the image half as many pixels wide as your terminal. For example, that is 60×40 for a 121×44 terminal.

https://twitter.com/0xabad1dea/status/561657011147120642

@sfan5
Copy link
Author

sfan5 commented Feb 1, 2015

If anyone uses that script: make the image half as many pixels wide as your terminal. For example, that is 60×40 for a 121×44 terminal.

Note that you don't need this if you use the newer revision with the -u flag.

@Albirew
Copy link

Albirew commented Feb 20, 2019

requirements for debian-like:

apt install python3-pip
pip3 install Pillow

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment