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