From 07c60aa33fbafb4f48c58404d525a3278b2b5f14 Mon Sep 17 00:00:00 2001 From: Falko Schindler Date: Wed, 22 Nov 2023 19:00:25 +0100 Subject: [PATCH 1/2] support PIL images as image source --- nicegui/elements/image.py | 27 +++++++++++++++++-- nicegui/elements/interactive_image.py | 10 ++++++- .../documentation/more/image_documentation.py | 10 +++++++ 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/nicegui/elements/image.py b/nicegui/elements/image.py index 32b1003c9..59caab08b 100644 --- a/nicegui/elements/image.py +++ b/nicegui/elements/image.py @@ -1,23 +1,46 @@ +import base64 +import io import time from pathlib import Path from typing import Union +from PIL.Image import Image as PIL_Image + from .mixins.source_element import SourceElement class Image(SourceElement, component='image.js'): - def __init__(self, source: Union[str, Path] = '') -> None: + def __init__(self, source: Union[str, Path, PIL_Image] = '') -> None: """Image Displays an image. This element is based on Quasar's `QImg `_ component. - :param source: the source of the image; can be a URL, local file path or a base64 string + :param source: the source of the image; can be a URL, local file path, a base64 string or a PIL image """ super().__init__(source=source) + def _set_props(self, source: Union[str, Path]) -> None: + if isinstance(source, PIL_Image): + source = image_to_base64(source) + super()._set_props(source) + def force_reload(self) -> None: """Force the image to reload from the source.""" self._props['t'] = time.time() self.update() + + +def image_to_base64(pil_image: PIL_Image, image_format: str = 'PNG') -> str: + """Convert a PIL image to a base64 string which can be used as image source. + + :param pil_image: the PIL image + :param image_format: the image format (default: 'PNG') + :return: the base64 string + """ + buffer = io.BytesIO() + pil_image.save(buffer, image_format) + base64_encoded = base64.b64encode(buffer.getvalue()) + base64_string = base64_encoded.decode('utf-8') + return f'data:image/{image_format.lower()};base64,{base64_string}' diff --git a/nicegui/elements/interactive_image.py b/nicegui/elements/interactive_image.py index 048179e37..0039d9a0f 100644 --- a/nicegui/elements/interactive_image.py +++ b/nicegui/elements/interactive_image.py @@ -1,10 +1,13 @@ from __future__ import annotations -import time +import time from pathlib import Path from typing import Any, Callable, List, Optional, Union, cast +from PIL.Image import Image as PIL_Image + from ..events import GenericEventArguments, MouseEventArguments, handle_event +from .image import image_to_base64 from .mixins.content_element import ContentElement from .mixins.source_element import SourceElement @@ -57,6 +60,11 @@ def handle_mouse(e: GenericEventArguments) -> None: handle_event(on_mouse, arguments) self.on('mouse', handle_mouse) + def _set_props(self, source: Union[str, Path]) -> None: + if isinstance(source, PIL_Image): + source = image_to_base64(source) + super()._set_props(source) + def force_reload(self) -> None: """Force the image to reload from the source.""" self._props['t'] = time.time() diff --git a/website/documentation/more/image_documentation.py b/website/documentation/more/image_documentation.py index 9fe8ab9c6..f9bcec63f 100644 --- a/website/documentation/more/image_documentation.py +++ b/website/documentation/more/image_documentation.py @@ -23,6 +23,16 @@ def base64(): base64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==' ui.image(base64).classes('w-2 h-2 m-auto') + @text_demo('PIL image', ''' + You can also use a PIL image as image source. + ''') + def pil(): + import numpy as np + from PIL import Image + + image = Image.fromarray(np.random.randint(0, 255, (100, 100), dtype=np.uint8)) + ui.image(image).classes('w-32') + @text_demo('Lottie files', ''' You can also use [Lottie files](https://lottiefiles.com/) with animations. ''') From 5dc3e99e53b817b79f312eaea12e13e0dea66c5b Mon Sep 17 00:00:00 2001 From: Falko Schindler Date: Thu, 23 Nov 2023 16:05:59 +0100 Subject: [PATCH 2/2] make image format configurable --- nicegui/elements/image.py | 7 ++++--- nicegui/elements/interactive_image.py | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/nicegui/elements/image.py b/nicegui/elements/image.py index 59caab08b..9968c3234 100644 --- a/nicegui/elements/image.py +++ b/nicegui/elements/image.py @@ -10,6 +10,7 @@ class Image(SourceElement, component='image.js'): + PIL_CONVERT_FORMAT = 'PNG' def __init__(self, source: Union[str, Path, PIL_Image] = '') -> None: """Image @@ -23,7 +24,7 @@ def __init__(self, source: Union[str, Path, PIL_Image] = '') -> None: def _set_props(self, source: Union[str, Path]) -> None: if isinstance(source, PIL_Image): - source = image_to_base64(source) + source = pil_to_base64(source, self.PIL_CONVERT_FORMAT) super()._set_props(source) def force_reload(self) -> None: @@ -32,11 +33,11 @@ def force_reload(self) -> None: self.update() -def image_to_base64(pil_image: PIL_Image, image_format: str = 'PNG') -> str: +def pil_to_base64(pil_image: PIL_Image, image_format: str) -> str: """Convert a PIL image to a base64 string which can be used as image source. :param pil_image: the PIL image - :param image_format: the image format (default: 'PNG') + :param image_format: the image format :return: the base64 string """ buffer = io.BytesIO() diff --git a/nicegui/elements/interactive_image.py b/nicegui/elements/interactive_image.py index 0039d9a0f..50fd240d0 100644 --- a/nicegui/elements/interactive_image.py +++ b/nicegui/elements/interactive_image.py @@ -7,13 +7,14 @@ from PIL.Image import Image as PIL_Image from ..events import GenericEventArguments, MouseEventArguments, handle_event -from .image import image_to_base64 +from .image import pil_to_base64 from .mixins.content_element import ContentElement from .mixins.source_element import SourceElement class InteractiveImage(SourceElement, ContentElement, component='interactive_image.js'): CONTENT_PROP = 'content' + PIL_CONVERT_FORMAT = 'PNG' def __init__(self, source: Union[str, Path] = '', *, @@ -62,7 +63,7 @@ def handle_mouse(e: GenericEventArguments) -> None: def _set_props(self, source: Union[str, Path]) -> None: if isinstance(source, PIL_Image): - source = image_to_base64(source) + source = pil_to_base64(source, self.PIL_CONVERT_FORMAT) super()._set_props(source) def force_reload(self) -> None: