Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support PIL images as image source #2069

Merged
merged 2 commits into from
Nov 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions nicegui/elements/image.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,47 @@
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'):
PIL_CONVERT_FORMAT = 'PNG'

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 <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)

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:
"""Force the image to reload from the source."""
self._props['t'] = time.time()
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}'
11 changes: 10 additions & 1 deletion nicegui/elements/interactive_image.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
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 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] = '', *,
Expand Down Expand Up @@ -57,6 +61,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 = pil_to_base64(source, self.PIL_CONVERT_FORMAT)
super()._set_props(source)

def force_reload(self) -> None:
"""Force the image to reload from the source."""
self._props['t'] = time.time()
Expand Down
10 changes: 10 additions & 0 deletions website/documentation/more/image_documentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
''')
Expand Down