diff options
Diffstat (limited to 'presentty/image.py')
-rw-r--r-- | presentty/image.py | 168 |
1 files changed, 168 insertions, 0 deletions
diff --git a/presentty/image.py b/presentty/image.py new file mode 100644 index 0000000..9aabee8 --- /dev/null +++ b/presentty/image.py | |||
@@ -0,0 +1,168 @@ | |||
1 | # Copyright (C) 2015 James E. Blair <corvus@gnu.org> | ||
2 | # | ||
3 | # This program is free software: you can redistribute it and/or modify | ||
4 | # it under the terms of the GNU General Public License as published by | ||
5 | # the Free Software Foundation, either version 3 of the License, or | ||
6 | # (at your option) any later version. | ||
7 | # | ||
8 | # This program is distributed in the hope that it will be useful, | ||
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
11 | # GNU General Public License for more details. | ||
12 | # | ||
13 | # You should have received a copy of the GNU General Public License | ||
14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
15 | |||
16 | import subprocess | ||
17 | import HTMLParser | ||
18 | import re | ||
19 | |||
20 | import PIL | ||
21 | import PIL.ExifTags | ||
22 | import urwid | ||
23 | |||
24 | import slide | ||
25 | |||
26 | def nearest_color(x): | ||
27 | if x < 0x30: return '0' | ||
28 | if x < 0x70: return '6' | ||
29 | if x < 0x98: return '8' | ||
30 | if x < 0xc0: return 'a' | ||
31 | if x < 0xe8: return 'd' | ||
32 | return 'f' | ||
33 | |||
34 | class ANSIImage(urwid.Widget): | ||
35 | def __init__(self, uri, hinter=None): | ||
36 | super(ANSIImage, self).__init__() | ||
37 | self.uri = uri | ||
38 | image = self._loadImage() | ||
39 | self.htmlparser = HTMLParser.HTMLParser() | ||
40 | self.ratio = float(image.size[0])/float(image.size[1]) | ||
41 | self.hinter = hinter | ||
42 | |||
43 | def _loadImage(self): | ||
44 | image = PIL.Image.open(self.uri) | ||
45 | image.load() | ||
46 | exif = image._getexif() | ||
47 | if exif: | ||
48 | orientation = exif.get(274, 1) | ||
49 | if orientation == 1: | ||
50 | pass | ||
51 | elif orientation == 3: | ||
52 | image = image.rotate(180) | ||
53 | elif orientation == 6: | ||
54 | image = image.rotate(-90) | ||
55 | elif orientation == 8: | ||
56 | image = image.rotate(90) | ||
57 | else: | ||
58 | raise Exception("unknown orientation %s" % orientation) | ||
59 | return image | ||
60 | |||
61 | def pack(self, size, focus=False): | ||
62 | cols = size[0] | ||
63 | if len(size) > 1: | ||
64 | rows = size[1] | ||
65 | elif self.hinter: | ||
66 | rows = self.hinter.getSize()[1] | ||
67 | else: | ||
68 | rows = None | ||
69 | width = cols | ||
70 | height = int(cols*(1.0/self.ratio)/2.0) | ||
71 | if rows is not None and height > rows: | ||
72 | height = rows | ||
73 | width = int(rows*self.ratio*2.0) | ||
74 | return (width, height) | ||
75 | |||
76 | def rows(self, size, focus=False): | ||
77 | r = self.pack(size) | ||
78 | return r[1] | ||
79 | |||
80 | SPAN_RE = re.compile(r"<span style='color:#(......); background-color:#(......);'>(.*)") | ||
81 | def render(self, size, focus=False): | ||
82 | spanre = self.SPAN_RE | ||
83 | htmlparser = self.htmlparser | ||
84 | width, height = self.pack(size, focus) | ||
85 | jp2a = subprocess.Popen(['jp2a', '--colors', '--fill', | ||
86 | '--width=%s' % width, | ||
87 | '--height=%s' % height, | ||
88 | '--html-raw', '-'], | ||
89 | stdin=subprocess.PIPE, | ||
90 | stdout=subprocess.PIPE, | ||
91 | stderr=subprocess.PIPE) | ||
92 | image = self._loadImage() | ||
93 | image.save(jp2a.stdin, 'JPEG') | ||
94 | jp2a.stdin.close() | ||
95 | data = jp2a.stdout.read() | ||
96 | jp2a.stderr.read() | ||
97 | jp2a.wait() | ||
98 | |||
99 | line_list = [] | ||
100 | attr_list = [] | ||
101 | line_text = '' | ||
102 | line_attrs = [] | ||
103 | current_attr = [None, 0] | ||
104 | current_fg = None | ||
105 | current_bg = None | ||
106 | current_props = None | ||
107 | for line in data.split('<br/>'): | ||
108 | if not line: | ||
109 | continue | ||
110 | for span in line.split('</span>'): | ||
111 | if not span: | ||
112 | continue | ||
113 | m = spanre.match(span) | ||
114 | fg, bg, char = m.groups() | ||
115 | if '&' in char: | ||
116 | char = htmlparser.unescape(char) | ||
117 | char = char.encode('utf8') | ||
118 | line_text += char | ||
119 | props = [] | ||
120 | # TODO: if bold is set, append bold to props | ||
121 | fg = ('#'+ | ||
122 | nearest_color(int(fg[0:2], 16)) + | ||
123 | nearest_color(int(fg[2:4], 16)) + | ||
124 | nearest_color(int(fg[4:6], 16))) | ||
125 | bg = ('#'+ | ||
126 | nearest_color(int(bg[0:2], 16)) + | ||
127 | nearest_color(int(bg[2:4], 16)) + | ||
128 | nearest_color(int(bg[4:6], 16))) | ||
129 | if current_fg == fg and current_bg == bg and current_props == props: | ||
130 | current_attr[1] += len(char) | ||
131 | else: | ||
132 | if current_attr[0]: | ||
133 | line_attrs.append(tuple(current_attr)) | ||
134 | fg = ', '.join(props + [fg]) | ||
135 | attr = urwid.AttrSpec(fg, bg) | ||
136 | current_attr = [attr, len(char)] | ||
137 | current_fg = fg | ||
138 | current_bg = bg | ||
139 | current_props = props | ||
140 | line_attrs.append(tuple(current_attr)) | ||
141 | current_attr = [None, 0] | ||
142 | current_fg = None | ||
143 | current_bg = None | ||
144 | line_list.append(line_text) | ||
145 | line_text = '' | ||
146 | attr_list.append(line_attrs) | ||
147 | line_attrs = [] | ||
148 | canvas = urwid.TextCanvas(line_list, attr_list) | ||
149 | return canvas | ||
150 | |||
151 | def main(): | ||
152 | import PIL.Image | ||
153 | img = PIL.Image.open('/tmp/p/8.jpg') | ||
154 | img.load() | ||
155 | hinter = slide.ScreenHinter() | ||
156 | hinter.set_cols_rows((80, 25)) | ||
157 | w = ANSIImage(img, hinter) | ||
158 | slpile = slide.SlidePile([]) | ||
159 | slpile.contents.append((w, slpile.options())) | ||
160 | pad = slide.SlidePadding(slpile, align='center', width='pack') | ||
161 | fill = slide.SlideFiller(pad) | ||
162 | #w.render((80,25)) | ||
163 | fill.render((80,25)) | ||
164 | screen = urwid.raw_display.Screen() | ||
165 | if True: | ||
166 | with screen.start(): | ||
167 | screen.draw_screen((80,25), fill.render((80,25))) | ||
168 | raw_input() | ||