From 232ef11630eed44d9f15b2e09b35bffb022c7afd Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sat, 7 Oct 2017 16:37:03 +0100 Subject: [PATCH] windows: Add an ImageWin. This adds an optional dependency on PIL. --- poezio/windows/__init__.py | 3 +- poezio/windows/image.py | 81 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 poezio/windows/image.py diff --git a/poezio/windows/__init__.py b/poezio/windows/__init__.py index 373349f4..56657433 100644 --- a/poezio/windows/__init__.py +++ b/poezio/windows/__init__.py @@ -18,6 +18,7 @@ from poezio.windows.misc import VerticalSeparator from poezio.windows.muc import UserList, Topic from poezio.windows.roster_win import RosterWin, ContactInfoWin from poezio.windows.text_win import TextWin, XMLTextWin +from poezio.windows.image import ImageWin __all__ = ['Win', 'FormWin', 'BookmarksWin', 'Dialog', 'GlobalInfoBar', 'VerticalGlobalInfoBar', 'InfoWin', 'PrivateInfoWin', 'XMLInfoWin', @@ -26,4 +27,4 @@ __all__ = ['Win', 'FormWin', 'BookmarksWin', 'Dialog', 'GlobalInfoBar', 'BookmarksInfoWin', 'ConfirmStatusWin', 'HelpText', 'Input', 'HistoryInput', 'MessageInput', 'CommandInput', 'ListWin', 'ColumnHeaderWin', 'VerticalSeparator', 'UserList', 'Topic', - 'RosterWin', 'ContactInfoWin', 'TextWin', 'XMLTextWin'] + 'RosterWin', 'ContactInfoWin', 'TextWin', 'XMLTextWin', 'ImageWin'] diff --git a/poezio/windows/image.py b/poezio/windows/image.py new file mode 100644 index 00000000..819227c5 --- /dev/null +++ b/poezio/windows/image.py @@ -0,0 +1,81 @@ +""" +Defines a window which contains either an image or a border. +""" + +import curses +from io import BytesIO + +try: + from PIL import Image + HAS_PIL = True +except ImportError: + HAS_PIL = False + +from poezio.windows.base_wins import Win +from poezio.theming import to_curses_attr +from poezio.xhtml import _parse_css_color + +class ImageWin(Win): + """ + A window which contains either an image or a border. + """ + def __init__(self): + self._image = None + Win.__init__(self) + + def resize(self, height: int, width: int, y: int, x: int): + self._resize(height, width, y, x) + if self._image is None: + return + _display_avatar(width, height) + + def refresh(self, data): + self._win.clear() + if data is None or not HAS_PIL: + self._image = None + self._display_border() + else: + image_file = BytesIO(data) + self._image = Image.open(image_file).convert('RGB') + self._display_avatar(self.width, self.height) + self._refresh() + + def _display_border(self): + self._win.border(curses.ACS_VLINE, curses.ACS_VLINE, + curses.ACS_HLINE, curses.ACS_HLINE, + curses.ACS_ULCORNER, curses.ACS_URCORNER, + curses.ACS_LLCORNER, curses.ACS_LRCORNER) + + @staticmethod + def _compute_size(image_size, width: int, height: int): + height *= 2 + src_width, src_height = image_size + ratio = src_width / src_height + new_width = height * ratio + new_height = width / ratio + if new_width > width: + height = int(new_height) + elif new_height > height: + width = int(new_width) + return width, height + + def _display_avatar(self, width: int, height: int): + original_height = height + original_width = width + size = self._compute_size(self._image.size, width, height) + image2 = self._image.resize(size, resample=Image.BILINEAR) + data: bytes = image2.tobytes() + width, height = size + start_y = (original_height - height // 2) // 2 + start_x = (original_width - width) // 2 + for y in range(height // 2): + two_lines = data[(2 * y) * width * 3: (2 * y + 2) * width * 3] + line1 = two_lines[:width * 3] + line2 = two_lines[width * 3:] + self.move(start_y + y, start_x) + for x in range(width): + r, g, b = line1[x * 3: (x + 1) * 3] + top_color = _parse_css_color('#%02x%02x%02x' % (r, g, b)) + r, g, b = line2[x * 3: (x + 1) * 3] + bot_color = _parse_css_color('#%02x%02x%02x' % (r, g, b)) + self.addstr('▄', to_curses_attr((bot_color, top_color)))