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