1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
|
"""
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 get_theme, to_curses_attr
from poezio.xhtml import _parse_css_color
from poezio.config import config
from typing import Tuple, Optional, Callable
class ImageWin(Win):
"""
A window which contains either an image or a border.
"""
def __init__(self) -> None:
self._image = None # type: Optional[Image]
Win.__init__(self)
if config.get('image_use_half_blocks'):
self._display_avatar = self._display_avatar_half_blocks # type: Callable[[int, int], None]
else:
self._display_avatar = self._display_avatar_full_blocks
def resize(self, height: int, width: int, y: int, x: int) -> None:
self._resize(height, width, y, x)
if self._image is None:
return
self._display_avatar(width, height)
def refresh(self, data: Optional[bytes]) -> None:
self._win.erase()
if data is not None and HAS_PIL:
image_file = BytesIO(data)
try:
image = Image.open(image_file)
except OSError:
self._display_border()
else:
self._image = image.convert('RGB')
self._display_avatar(self.width, self.height)
else:
self._display_border()
self._refresh()
def _display_border(self) -> None:
self._image = None
attribute = to_curses_attr(get_theme().COLOR_VERTICAL_SEPARATOR)
self._win.attron(attribute)
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)
self._win.attroff(attribute)
@staticmethod
def _compute_size(image_size: Tuple[int, int], width: int, height: int) -> Tuple[int, 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_half_blocks(self, width: int, height: int) -> None:
if self._image is None:
return
original_height = height
original_width = width
size = self._compute_size(self._image.size, width, height)
image2 = self._image.resize(size, resample=Image.BILINEAR)
data = 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(0, width * 3, 3):
r, g, b = line1[x:x + 3]
top_color = _parse_css_color('#%02x%02x%02x' % (r, g, b))
r, g, b = line2[x:x + 3]
bot_color = _parse_css_color('#%02x%02x%02x' % (r, g, b))
self.addstr('▄', to_curses_attr((bot_color, top_color)))
def _display_avatar_full_blocks(self, width: int, height: int) -> None:
if self._image is None:
return
original_height = height
original_width = width
width, height = self._compute_size(self._image.size, width, height)
height //= 2
size = width, height
image2 = self._image.resize(size, resample=Image.BILINEAR)
data = image2.tobytes()
start_y = (original_height - height) // 2
start_x = (original_width - width) // 2
for y in range(height):
line = data[y * width * 3:(y + 1) * width * 3]
self.move(start_y + y, start_x)
for x in range(0, width * 3, 3):
r, g, b = line[x:x + 3]
color = _parse_css_color('#%02x%02x%02x' % (r, g, b))
self.addstr('█', to_curses_attr((color, -1)))
|