summaryrefslogtreecommitdiff
path: root/presentty/image.py
diff options
context:
space:
mode:
Diffstat (limited to 'presentty/image.py')
-rw-r--r--presentty/image.py168
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
16import subprocess
17import HTMLParser
18import re
19
20import PIL
21import PIL.ExifTags
22import urwid
23
24import slide
25
26def 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
34class 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
151def 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()