Browse Source

Merge pull request #2069 from zauberzeug/pil

Support PIL images as image source
Falko Schindler 1 year ago
parent
commit
59546fb992

+ 26 - 2
nicegui/elements/image.py

@@ -1,23 +1,47 @@
+import base64
+import io
 import time
 import time
 from pathlib import Path
 from pathlib import Path
 from typing import Union
 from typing import Union
 
 
+from PIL.Image import Image as PIL_Image
+
 from .mixins.source_element import SourceElement
 from .mixins.source_element import SourceElement
 
 
 
 
 class Image(SourceElement, component='image.js'):
 class Image(SourceElement, component='image.js'):
+    PIL_CONVERT_FORMAT = 'PNG'
 
 
-    def __init__(self, source: Union[str, Path] = '') -> None:
+    def __init__(self, source: Union[str, Path, PIL_Image] = '') -> None:
         """Image
         """Image
 
 
         Displays an image.
         Displays an image.
         This element is based on Quasar's `QImg <https://quasar.dev/vue-components/img>`_ component.
         This element is based on Quasar's `QImg <https://quasar.dev/vue-components/img>`_ 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)
         super().__init__(source=source)
 
 
+    def _set_props(self, source: Union[str, Path]) -> None:
+        if isinstance(source, PIL_Image):
+            source = pil_to_base64(source, self.PIL_CONVERT_FORMAT)
+        super()._set_props(source)
+
     def force_reload(self) -> None:
     def force_reload(self) -> None:
         """Force the image to reload from the source."""
         """Force the image to reload from the source."""
         self._props['t'] = time.time()
         self._props['t'] = time.time()
         self.update()
         self.update()
+
+
+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
+    :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}'

+ 10 - 1
nicegui/elements/interactive_image.py

@@ -1,16 +1,20 @@
 from __future__ import annotations
 from __future__ import annotations
-import time
 
 
+import time
 from pathlib import Path
 from pathlib import Path
 from typing import Any, Callable, List, Optional, Union, cast
 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 ..events import GenericEventArguments, MouseEventArguments, handle_event
+from .image import pil_to_base64
 from .mixins.content_element import ContentElement
 from .mixins.content_element import ContentElement
 from .mixins.source_element import SourceElement
 from .mixins.source_element import SourceElement
 
 
 
 
 class InteractiveImage(SourceElement, ContentElement, component='interactive_image.js'):
 class InteractiveImage(SourceElement, ContentElement, component='interactive_image.js'):
     CONTENT_PROP = 'content'
     CONTENT_PROP = 'content'
+    PIL_CONVERT_FORMAT = 'PNG'
 
 
     def __init__(self,
     def __init__(self,
                  source: Union[str, Path] = '', *,
                  source: Union[str, Path] = '', *,
@@ -57,6 +61,11 @@ class InteractiveImage(SourceElement, ContentElement, component='interactive_ima
             handle_event(on_mouse, arguments)
             handle_event(on_mouse, arguments)
         self.on('mouse', handle_mouse)
         self.on('mouse', handle_mouse)
 
 
+    def _set_props(self, source: Union[str, Path]) -> None:
+        if isinstance(source, PIL_Image):
+            source = pil_to_base64(source, self.PIL_CONVERT_FORMAT)
+        super()._set_props(source)
+
     def force_reload(self) -> None:
     def force_reload(self) -> None:
         """Force the image to reload from the source."""
         """Force the image to reload from the source."""
         self._props['t'] = time.time()
         self._props['t'] = time.time()

+ 10 - 0
website/documentation/more/image_documentation.py

@@ -23,6 +23,16 @@ def more() -> None:
         base64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='
         base64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='
         ui.image(base64).classes('w-2 h-2 m-auto')
         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', '''
     @text_demo('Lottie files', '''
         You can also use [Lottie files](https://lottiefiles.com/) with animations.
         You can also use [Lottie files](https://lottiefiles.com/) with animations.
     ''')
     ''')