AvatarWin: Add SVG support.

This commit is contained in:
Emmanuel Gil Peyrot 2019-01-24 20:08:21 +01:00
parent ed8224a3a5
commit d19046b5b1

View file

@ -11,6 +11,15 @@ try:
except ImportError:
HAS_PIL = False
try:
import gi
gi.require_version('Rsvg', '2.0')
from gi.repository import Rsvg
import cairo
HAS_RSVG = True
except (ImportError, ValueError):
HAS_RSVG = False
from poezio.windows.base_wins import Win
from poezio.theming import get_theme, to_curses_attr
from poezio.xhtml import _parse_css_color
@ -19,6 +28,26 @@ from poezio.config import config
from typing import Tuple, Optional, Callable
def render_from_svg(svg: bytes) -> Optional[Image.Image]:
if not HAS_RSVG:
return None
try:
handle = Rsvg.Handle.new_from_data(svg)
dimensions = handle.get_dimensions()
surface = cairo.ImageSurface(cairo.Format.ARGB32, dimensions.width, dimensions.height)
context = cairo.Context(surface)
handle.render_cairo(context)
data = surface.get_data()
image = Image.frombytes('RGBA', (dimensions.width, dimensions.height), data.tobytes())
# This is required because Cairo uses a BGRA (in host endianess)
# format, and PIL an ABGR (in byte order) format. Yes, this is
# confusing.
b, g, r, a = image.split()
return Image.merge('RGB', (r, g, b))
except Exception:
return None
class ImageWin(Win):
"""
A window which contains either an image or a border.
@ -27,7 +56,7 @@ class ImageWin(Win):
__slots__ = ('_image', '_display_avatar')
def __init__(self) -> None:
self._image = None # type: Optional[Image]
self._image = None # type: Optional[Image.Image]
Win.__init__(self)
if config.get('image_use_half_blocks'):
self._display_avatar = self._display_avatar_half_blocks # type: Callable[[int, int], None]
@ -45,7 +74,14 @@ class ImageWin(Win):
if data is not None and HAS_PIL:
image_file = BytesIO(data)
try:
image = Image.open(image_file)
try:
image = Image.open(image_file)
except OSError:
# TODO: Make the caller pass the MIME type, so we dont
# have to try all renderers like that.
image = render_from_svg(data)
if image is None:
raise
except OSError:
self._display_border()
else: