binary_search with no luck
[rainbowstream.git] / rainbowstream / image.py
CommitLineData
026caf17 1from PIL import Image,ImageFilter
2b502606 2import sys,os
026caf17 3from functools import partial
1c50e2d1
O
4
5""" Convert values between RGB hex codes and xterm-256 color codes.
6
7Nice long listing of all 256 colors and their codes. Useful for
8developing console color themes, or even script output schemes.
9
10Resources:
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
15I'm not sure where this script was inspired from. I think I must have
16written 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
26import sys, re
27
28CLUT = [ # 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'),
026caf17 279
1c50e2d1
O
280 ('241', '626262'),
281 ('242', '6c6c6c'),
282 ('243', '767676'),
283 ('244', '808080'),
026caf17 284
1c50e2d1
O
285 ('245', '8a8a8a'),
286 ('246', '949494'),
287 ('247', '9e9e9e'),
288 ('248', 'a8a8a8'),
026caf17 289
1c50e2d1
O
290 ('249', 'b2b2b2'),
291 ('250', 'bcbcbc'),
292 ('251', 'c6c6c6'),
293 ('252', 'd0d0d0'),
294 ('253', 'dadada'),
026caf17 295
1c50e2d1
O
296 ('254', 'e4e4e4'),
297 ('255', 'eeeeee'),
298]
299
1c50e2d1
O
300def _strip_hash(rgb):
301 # Strip leading `#` if exists.
302 if rgb.startswith('#'):
303 rgb = rgb.lstrip('#')
304 return rgb
305
306def _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
313def short2rgb(short):
314 return SHORT2RGB_DICT[short]
315
2b502606 316def pixel_print(ansicolor):
317 sys.stdout.write('\033[48;5;%sm \033[0m' % (ansicolor))
318
25e5e7f4 319def 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
324def rgb_to_hex(rgb):
325 return '#%02x%02x%02x' % rgb
326
026caf17 327def 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
1c50e2d1
O
341def 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.
1c50e2d1
O
345 """
346 rgb = _strip_hash(rgb)
2b502606 347 r = int(rgb[:2],16)
348 g = int(rgb[2:4],16)
349 b = int(rgb[4:],16)
026caf17 350 dist = lambda s,d: (s[0]-d[0])**2+(s[1]-d[1])**2+(s[2]-d[2])**2
2b502606 351 ary = [hex_to_rgb(hex) for hex in RGB2SHORT_DICT]
026caf17 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))]
2b502606 355
026caf17 356
2b502606 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
026caf17 377#
1c50e2d1
O
378RGB2SHORT_DICT, SHORT2RGB_DICT = _create_dicts()
379
2b502606 380def image_to_display(path):
25e5e7f4 381 i = Image.open(path)
2b502606 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 ''
25e5e7f4 399
1c50e2d1
O
400#---------------------------------------------------------------------
401
402if __name__ == '__main__':
2b502606 403 path = sys.argv[1]
404 image_to_display(path)
026caf17 405