Skip to content

Instantly share code, notes, and snippets.

@atupal
Created April 12, 2013 13:06
Show Gist options
  • Save atupal/5371864 to your computer and use it in GitHub Desktop.
Save atupal/5371864 to your computer and use it in GitHub Desktop.
/**
* Optimized Code For Quantizing Colors to xterm256
*
* These functions are equivalent to the ones found in xterm256.py but
* orders of a magnitude faster and should compile quickly (fractions
* of a second) on most systems with very little risk of
* complications.
*
* Color quantization is very complex. This works by treating RGB
* values as 3D euclidean space and brute-force searching for the
* nearest neighbor.
*/
typedef struct {
int r;
int g;
int b;
} rgb_t;
int CUBE_STEPS[] = { 0x00, 0x5F, 0x87, 0xAF, 0xD7, 0xFF };
rgb_t BASIC16[] = { { 0, 0, 0 }, { 205, 0, 0}, { 0, 205, 0 },
{ 205, 205, 0 }, { 0, 0, 238}, { 205, 0, 205 },
{ 0, 205, 205 }, { 229, 229, 229}, { 127, 127, 127 },
{ 255, 0, 0 }, { 0, 255, 0}, { 255, 255, 0 },
{ 92, 92, 255 }, { 255, 0, 255}, { 0, 255, 255 },
{ 255, 255, 255 } };
rgb_t COLOR_TABLE[256];
rgb_t xterm_to_rgb(int xcolor)
{
rgb_t res;
if (xcolor < 16) {
res = BASIC16[xcolor];
} else if (16 <= xcolor && xcolor <= 231) {
xcolor -= 16;
res.r = CUBE_STEPS[(xcolor / 36) % 6];
res.g = CUBE_STEPS[(xcolor / 6) % 6];
res.b = CUBE_STEPS[xcolor % 6];
} else if (232 <= xcolor && xcolor <= 255) {
res.r = res.g = res.b = 8 + (xcolor - 232) * 0x0A;
}
return res;
}
/**
* This function provides a quick and dirty way to serialize an rgb_t
* struct to an int which can be decoded by our Python code using
* ctypes.
*/
int xterm_to_rgb_i(int xcolor)
{
rgb_t res = xterm_to_rgb(xcolor);
return (res.r << 16) | (res.g << 8) | (res.b << 0);
}
#define sqr(x) ((x) * (x))
/**
* Quantize RGB values to an xterm 256-color ID
*/
int rgb_to_xterm(int r, int g, int b)
{
int best_match = 0;
int smallest_distance = 1000000000;
int c, d;
for (c = 16; c < 256; c++) {
d = sqr(COLOR_TABLE[c].r - r) +
sqr(COLOR_TABLE[c].g - g) +
sqr(COLOR_TABLE[c].b - b);
if (d < smallest_distance) {
smallest_distance = d;
best_match = c;
}
}
return best_match;
}
int init()
{
int c;
for (c = 0; c < 256; c++) {
COLOR_TABLE[c] = xterm_to_rgb(c);
}
return 0;
}
#!/usr/bin/python2
# -*- coding: utf-8 -*-
import Image
import sys
import getopt
import time
bash = False
width = 0
height = 0
imgWidth = 0
imgHeight = 0
character = u'▄'
verbose = False
native = "_xterm256.c"
CUBE_STEPS = [0x00, 0x5F, 0x87, 0xAF, 0xD7, 0xFF]
BASIC16 = ((0, 0, 0), (205, 0, 0), (0, 205, 0), (205, 205, 0),
(0, 0, 238), (205, 0, 205), (0, 205, 205), (229, 229, 229),
(127, 127, 127), (255, 0, 0), (0, 255, 0), (255, 255, 0),
(92, 92, 255), (255, 0, 255), (0, 255, 255), (255, 255, 255))
def usage():
print("\n "+sys.argv[0]+" [-whcbvn] image")
print(" Options:")
print(" -w --width - width in pixels, if specified without height, the image")
print(" will be scaled proportionally")
print(" -h --height - see width")
print(" -c --character - character to use for foreground")
print(" -b --as-bash-script - output is a bash script")
print(" -v --verbose - verbose output")
print(" -n --native - name of the C-file which will be used to replace xterm_to_rgb")
print(" and rgb_to_xterm by native methods")
print(" --help - this")
print(" image - Any image or gif (output will be animated)\n")
def xterm_to_rgb(xcolor):
assert 0 <= xcolor <= 255
if xcolor < 16:
# basic colors
return BASIC16[xcolor]
elif 16 <= xcolor <= 231:
# color cube
xcolor -= 16
return (CUBE_STEPS[(xcolor / 36) % 6],
CUBE_STEPS[(xcolor / 6) % 6],
CUBE_STEPS[xcolor % 6])
elif 232 <= xcolor <= 255:
# gray tone
c = 8 + (xcolor - 232) * 0x0A
return (c, c, c)
COLOR_TABLE = [xterm_to_rgb(i) for i in xrange(256)]
def rgb_to_xterm(r, g, b):
if r < 5 and g < 5 and b < 5:
return 16
best_match = 0
smallest_distance = 10000000000
for c in xrange(16, 256):
d = (COLOR_TABLE[c][0] - r) ** 2 + \
(COLOR_TABLE[c][1] - g) ** 2 + \
(COLOR_TABLE[c][2] - b) ** 2
if d < smallest_distance:
smallest_distance = d
best_match = c
return best_match
def printPixels(rgb1,rgb2):
c1 = rgb_to_xterm(rgb1[0], rgb1[1],rgb1[2])
c2 = rgb_to_xterm(rgb2[0], rgb2[1],rgb2[2])
sys.stdout.write('\x1b[48;5;%d;38;5;%dm' % (c1, c2))
sys.stdout.write(character)
def printImage(im):
global line
for y in range(0,height-1,2):
for x in range(width):
p1 = im.getpixel((x,y))
p2 = im.getpixel((x,y+1))
printPixels(p1, p2)
print('\x1b[0m')
def iterateImages(im):
if bash:
print("echo '\x1b[s'")
else:
sys.stdout.write('\x1b[s')
while True:
if bash:
print('cat <<"EOF"')
sys.stdout.write('\x1b[u')
printImage(getFrame(im))
if bash:
print("EOF")
try:
im.seek(im.tell()+1)
if bash:
print('sleep '+str(im.info['duration']/1000.0))
else:
time.sleep(im.info['duration']/1000.0)
except EOFError:
break
def getFrame(im):
if width!=imgWidth or height!=imgHeight:
return im.resize((width,height), Image.ANTIALIAS).convert('RGB')
else:
return im.convert('RGB')
def compile_speedup():
import os
import ctypes
from os.path import join, dirname, getmtime, exists, expanduser
# library = join(dirname(__file__), '_xterm256.so')
library = expanduser('~/.xterm256.so')
sauce = join(dirname(__file__), native)
if not exists(library) or getmtime(sauce) > getmtime(library):
build = "gcc -fPIC -shared -o %s %s" % (library, sauce)
assert os.system(build + " >/dev/null 2>&1") == 0
xterm256_c = ctypes.cdll.LoadLibrary(library)
xterm256_c.init()
def xterm_to_rgb(xcolor):
res = xterm256_c.xterm_to_rgb_i(xcolor)
return ((res >> 16) & 0xFF, (res >> 8) & 0xFF, res & 0xFF)
return (xterm256_c.rgb_to_xterm, xterm_to_rgb)
try:
opts, args = getopt.getopt(sys.argv[1:], "w:h:c:n:bv",["width=","height=","character=","native=","as-bash-script","help","verbose"])
except:
usage()
sys.exit(1)
for o, a in opts:
if o in ("-b", "as-bash-script"):
bash = True
elif o in ("-w", "--width"):
width = int(a)
elif o in ("-h", "--height"):
height = int(a)
elif o in ("-c", "--character"):
character = a
elif o in ("-v", "--verbose"):
verbose = True
elif o in ("-n", "--native"):
native = a
elif o in ("--help"):
usage()
sys.exit(0)
if(len(args)==0):
print("No image given")
usage()
sys.exit(1)
im = Image.open(args[0])
imgWidth = im.size[0]
imgHeight = im.size[1]
try:
(rgb_to_xterm, xterm_to_rgb) = compile_speedup()
except:
if verbose and not bash:
print("Failed to compile code, no speedup")
if width!=0 or height!=0:
if width==0:
width=int(imgWidth*(height/float(imgHeight)))
if height==0:
height=int(imgHeight*(width/float(imgWidth)))
else:
width = imgWidth
height = imgHeight
iterateImages(im)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment