fa76f20ab3c519836bb0d67618b75db9bba928cf
[rainbowstream.git] / rainbowstream / image.py
1 from PIL import Image,ImageFilter
2 import sys,os
3 from functools import partial
4
5 """ Convert values between RGB hex codes and xterm-256 color codes.
6
7 Nice long listing of all 256 colors and their codes. Useful for
8 developing console color themes, or even script output schemes.
9
10 Resources:
11 * http://en.wikipedia.org/wiki/8-bit_color
12 * http://en.wikipedia.org/wiki/ANSI_escape_code
13 * /usr/share/X11/rgb.txt
14
15 I'm not sure where this script was inspired from. I think I must have
16 written it from scratch, though it's been several years now.
17 """
18
19 __author__ = 'Micah Elliott http://MicahElliott.com'
20 __version__ = '0.1'
21 __copyright__ = 'Copyright (C) 2011 Micah Elliott. All rights reserved.'
22 __license__ = 'WTFPL http://sam.zoy.org/wtfpl/'
23
24 #---------------------------------------------------------------------
25
26 import sys, re
27
28 CLUT = [ # color look-up table
29 # 8-bit, RGB hex
30
31 # Primary 3-bit (8 colors). Unique representation!
32 ('00', '000000'),
33 ('01', '800000'),
34 ('02', '008000'),
35 ('03', '808000'),
36 ('04', '000080'),
37 ('05', '800080'),
38 ('06', '008080'),
39 ('07', 'c0c0c0'),
40
41 # Equivalent "bright" versions of original 8 colors.
42 ('08', '808080'),
43 ('09', 'ff0000'),
44 ('10', '00ff00'),
45 ('11', 'ffff00'),
46 ('12', '0000ff'),
47 ('13', 'ff00ff'),
48 ('14', '00ffff'),
49 ('15', 'ffffff'),
50
51 # Strictly ascending.
52 ('16', '000000'),
53 ('17', '00005f'),
54 ('18', '000087'),
55 ('19', '0000af'),
56 ('20', '0000d7'),
57 ('21', '0000ff'),
58 ('22', '005f00'),
59 ('23', '005f5f'),
60 ('24', '005f87'),
61 ('25', '005faf'),
62 ('26', '005fd7'),
63 ('27', '005fff'),
64 ('28', '008700'),
65 ('29', '00875f'),
66 ('30', '008787'),
67 ('31', '0087af'),
68 ('32', '0087d7'),
69 ('33', '0087ff'),
70 ('34', '00af00'),
71 ('35', '00af5f'),
72 ('36', '00af87'),
73 ('37', '00afaf'),
74 ('38', '00afd7'),
75 ('39', '00afff'),
76 ('40', '00d700'),
77 ('41', '00d75f'),
78 ('42', '00d787'),
79 ('43', '00d7af'),
80 ('44', '00d7d7'),
81 ('45', '00d7ff'),
82 ('46', '00ff00'),
83 ('47', '00ff5f'),
84 ('48', '00ff87'),
85 ('49', '00ffaf'),
86 ('50', '00ffd7'),
87 ('51', '00ffff'),
88 ('52', '5f0000'),
89 ('53', '5f005f'),
90 ('54', '5f0087'),
91 ('55', '5f00af'),
92 ('56', '5f00d7'),
93 ('57', '5f00ff'),
94 ('58', '5f5f00'),
95 ('59', '5f5f5f'),
96 ('60', '5f5f87'),
97 ('61', '5f5faf'),
98 ('62', '5f5fd7'),
99 ('63', '5f5fff'),
100 ('64', '5f8700'),
101 ('65', '5f875f'),
102 ('66', '5f8787'),
103 ('67', '5f87af'),
104 ('68', '5f87d7'),
105 ('69', '5f87ff'),
106 ('70', '5faf00'),
107 ('71', '5faf5f'),
108 ('72', '5faf87'),
109 ('73', '5fafaf'),
110 ('74', '5fafd7'),
111 ('75', '5fafff'),
112 ('76', '5fd700'),
113 ('77', '5fd75f'),
114 ('78', '5fd787'),
115 ('79', '5fd7af'),
116 ('80', '5fd7d7'),
117 ('81', '5fd7ff'),
118 ('82', '5fff00'),
119 ('83', '5fff5f'),
120 ('84', '5fff87'),
121 ('85', '5fffaf'),
122 ('86', '5fffd7'),
123 ('87', '5fffff'),
124 ('88', '870000'),
125 ('89', '87005f'),
126 ('90', '870087'),
127 ('91', '8700af'),
128 ('92', '8700d7'),
129 ('93', '8700ff'),
130 ('94', '875f00'),
131 ('95', '875f5f'),
132 ('96', '875f87'),
133 ('97', '875faf'),
134 ('98', '875fd7'),
135 ('99', '875fff'),
136 ('100', '878700'),
137 ('101', '87875f'),
138 ('102', '878787'),
139 ('103', '8787af'),
140 ('104', '8787d7'),
141 ('105', '8787ff'),
142 ('106', '87af00'),
143 ('107', '87af5f'),
144 ('108', '87af87'),
145 ('109', '87afaf'),
146 ('110', '87afd7'),
147 ('111', '87afff'),
148 ('112', '87d700'),
149 ('113', '87d75f'),
150 ('114', '87d787'),
151 ('115', '87d7af'),
152 ('116', '87d7d7'),
153 ('117', '87d7ff'),
154 ('118', '87ff00'),
155 ('119', '87ff5f'),
156 ('120', '87ff87'),
157 ('121', '87ffaf'),
158 ('122', '87ffd7'),
159 ('123', '87ffff'),
160 ('124', 'af0000'),
161 ('125', 'af005f'),
162 ('126', 'af0087'),
163 ('127', 'af00af'),
164 ('128', 'af00d7'),
165 ('129', 'af00ff'),
166 ('130', 'af5f00'),
167 ('131', 'af5f5f'),
168 ('132', 'af5f87'),
169 ('133', 'af5faf'),
170 ('134', 'af5fd7'),
171 ('135', 'af5fff'),
172 ('136', 'af8700'),
173 ('137', 'af875f'),
174 ('138', 'af8787'),
175 ('139', 'af87af'),
176 ('140', 'af87d7'),
177 ('141', 'af87ff'),
178 ('142', 'afaf00'),
179 ('143', 'afaf5f'),
180 ('144', 'afaf87'),
181 ('145', 'afafaf'),
182 ('146', 'afafd7'),
183 ('147', 'afafff'),
184 ('148', 'afd700'),
185 ('149', 'afd75f'),
186 ('150', 'afd787'),
187 ('151', 'afd7af'),
188 ('152', 'afd7d7'),
189 ('153', 'afd7ff'),
190 ('154', 'afff00'),
191 ('155', 'afff5f'),
192 ('156', 'afff87'),
193 ('157', 'afffaf'),
194 ('158', 'afffd7'),
195 ('159', 'afffff'),
196 ('160', 'd70000'),
197 ('161', 'd7005f'),
198 ('162', 'd70087'),
199 ('163', 'd700af'),
200 ('164', 'd700d7'),
201 ('165', 'd700ff'),
202 ('166', 'd75f00'),
203 ('167', 'd75f5f'),
204 ('168', 'd75f87'),
205 ('169', 'd75faf'),
206 ('170', 'd75fd7'),
207 ('171', 'd75fff'),
208 ('172', 'd78700'),
209 ('173', 'd7875f'),
210 ('174', 'd78787'),
211 ('175', 'd787af'),
212 ('176', 'd787d7'),
213 ('177', 'd787ff'),
214 ('178', 'd7af00'),
215 ('179', 'd7af5f'),
216 ('180', 'd7af87'),
217 ('181', 'd7afaf'),
218 ('182', 'd7afd7'),
219 ('183', 'd7afff'),
220 ('184', 'd7d700'),
221 ('185', 'd7d75f'),
222 ('186', 'd7d787'),
223 ('187', 'd7d7af'),
224 ('188', 'd7d7d7'),
225 ('189', 'd7d7ff'),
226 ('190', 'd7ff00'),
227 ('191', 'd7ff5f'),
228 ('192', 'd7ff87'),
229 ('193', 'd7ffaf'),
230 ('194', 'd7ffd7'),
231 ('195', 'd7ffff'),
232 ('196', 'ff0000'),
233 ('197', 'ff005f'),
234 ('198', 'ff0087'),
235 ('199', 'ff00af'),
236 ('200', 'ff00d7'),
237 ('201', 'ff00ff'),
238 ('202', 'ff5f00'),
239 ('203', 'ff5f5f'),
240 ('204', 'ff5f87'),
241 ('205', 'ff5faf'),
242 ('206', 'ff5fd7'),
243 ('207', 'ff5fff'),
244 ('208', 'ff8700'),
245 ('209', 'ff875f'),
246 ('210', 'ff8787'),
247 ('211', 'ff87af'),
248 ('212', 'ff87d7'),
249 ('213', 'ff87ff'),
250 ('214', 'ffaf00'),
251 ('215', 'ffaf5f'),
252 ('216', 'ffaf87'),
253 ('217', 'ffafaf'),
254 ('218', 'ffafd7'),
255 ('219', 'ffafff'),
256 ('220', 'ffd700'),
257 ('221', 'ffd75f'),
258 ('222', 'ffd787'),
259 ('223', 'ffd7af'),
260 ('224', 'ffd7d7'),
261 ('225', 'ffd7ff'),
262 ('226', 'ffff00'),
263 ('227', 'ffff5f'),
264 ('228', 'ffff87'),
265 ('229', 'ffffaf'),
266 ('230', 'ffffd7'),
267 ('231', 'ffffff'),
268
269 # Gray-scale range.
270 ('232', '080808'),
271 ('233', '121212'),
272 ('234', '1c1c1c'),
273 ('235', '262626'),
274 ('236', '303030'),
275 ('237', '3a3a3a'),
276 ('238', '444444'),
277 ('239', '4e4e4e'),
278 ('240', '585858'),
279
280 ('241', '626262'),
281 ('242', '6c6c6c'),
282 ('243', '767676'),
283 ('244', '808080'),
284
285 ('245', '8a8a8a'),
286 ('246', '949494'),
287 ('247', '9e9e9e'),
288 ('248', 'a8a8a8'),
289
290 ('249', 'b2b2b2'),
291 ('250', 'bcbcbc'),
292 ('251', 'c6c6c6'),
293 ('252', 'd0d0d0'),
294 ('253', 'dadada'),
295
296 ('254', 'e4e4e4'),
297 ('255', 'eeeeee'),
298 ]
299
300 def _strip_hash(rgb):
301 # Strip leading `#` if exists.
302 if rgb.startswith('#'):
303 rgb = rgb.lstrip('#')
304 return rgb
305
306 def _create_dicts():
307 short2rgb_dict = dict(CLUT)
308 rgb2short_dict = {}
309 for k, v in short2rgb_dict.items():
310 rgb2short_dict[v] = k
311 return rgb2short_dict, short2rgb_dict
312
313 def short2rgb(short):
314 return SHORT2RGB_DICT[short]
315
316 def pixel_print(ansicolor):
317 sys.stdout.write('\033[48;5;%sm \033[0m' % (ansicolor))
318
319 def hex_to_rgb(value):
320 value = value.lstrip('#')
321 lv = len(value)
322 return tuple(int(value[i:i+lv/3], 16) for i in range(0, lv, lv/3))
323
324 def rgb_to_hex(rgb):
325 return '#%02x%02x%02x' % rgb
326
327 def binary_search(ary,(r,g,b)):
328 ary.sort()
329 if len(ary) == 1 : return ary[0]
330 mid = len(ary)//2
331 left = binary_search(ary[:mid],(r,g,b))
332 right = binary_search(ary[mid:],(r,g,b))
333 ld = (left[0]-r)**2 + (left[1]-g)**2 + (left[2]-b)**2
334 rd = (right[0]-r)**2 + (right[1]-g)**2 + (right[2]-b)**2
335 if ld < rd :
336 return left
337 else:
338 return right
339
340
341 def rgb2short(rgb):
342 """ Find the closest xterm-256 approximation to the given RGB value.
343 @param rgb: Hex code representing an RGB value, eg, 'abcdef'
344 @returns: String between 0 and 255, compatible with xterm.
345 """
346 rgb = _strip_hash(rgb)
347 r = int(rgb[:2],16)
348 g = int(rgb[2:4],16)
349 b = int(rgb[4:],16)
350 dist = lambda s,d: (s[0]-d[0])**2+(s[1]-d[1])**2+(s[2]-d[2])**2
351 ary = [hex_to_rgb(hex) for hex in RGB2SHORT_DICT]
352 m = binary_search(ary,(r,g,b))
353 m = min(ary, key=partial(dist, (r,g,b)))
354 return RGB2SHORT_DICT[_strip_hash(rgb_to_hex(m))]
355
356
357 # incs = (0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff)
358 # parts = [ int(h, 16) for h in re.split(r'(..)(..)(..)', rgb)[1:4] ]
359 # res = []
360 # for part in parts:
361 # i = 0
362 # while i < len(incs)-1:
363 # s, b = incs[i], incs[i+1] # smaller, bigger
364 # if s <= part <= b:
365 # s1 = abs(s - part)
366 # b1 = abs(b - part)
367 # if s1 < b1: closest = s
368 # else: closest = b
369 # res.append(closest)
370 # break
371 # i += 1
372 # #print '***', res
373 # res = ''.join([ ('%02.x' % i) for i in res ])
374 # equiv = RGB2SHORT_DICT[ res ]
375 # #print '***', res, equiv
376 # return equiv
377 #
378 RGB2SHORT_DICT, SHORT2RGB_DICT = _create_dicts()
379
380 def image_to_display(path):
381 i = Image.open(path)
382 i = i.convert('RGBA')
383 w,h = i.size
384 i.load()
385 rows, columns = os.popen('stty size', 'r').read().split()
386 width = min(w, int(columns)-2*6)
387 height = int(float(h) * (float(width) / float(w)))
388 height //= 2
389 i = i.resize((width, height), Image.BICUBIC)
390
391 for y in xrange(height):
392 print ' '*6 ,
393 for x in xrange(width):
394 p = i.getpixel((x,y))
395 r, g, b = p[:3]
396 hex = rgb_to_hex((r,g,b))
397 pixel_print(rgb2short(hex))
398 print ''
399
400 #---------------------------------------------------------------------
401
402 if __name__ == '__main__':
403 path = sys.argv[1]
404 image_to_display(path)
405