Skip to content

Commit

Permalink
Merge pull request #1588 from bruno-rino/image_from_bytes
Browse files Browse the repository at this point in the history
Load Image from bytes
  • Loading branch information
freakboy3742 authored Oct 5, 2022
2 parents 2d76076 + f9ec63c commit 9a292f8
Show file tree
Hide file tree
Showing 13 changed files with 90 additions and 17 deletions.
18 changes: 18 additions & 0 deletions examples/imageview/imageview/app.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import io

import toga
from toga.style.pack import CENTER, COLUMN

from PIL import Image, ImageDraw


class ImageViewApp(toga.App):
def startup(self):
Expand All @@ -26,6 +30,20 @@ def startup(self):
imageview_from_pathlib_path.style.update(height=72)
box.add(imageview_from_pathlib_path)

# image from bytes
# generate an image using pillow
img = Image.new('RGBA', size=(110, 30))
d1 = ImageDraw.Draw(img)
d1.text((20, 10), "Pillow image", fill='green')
# get png bytes
buffer = io.BytesIO()
img.save(buffer, format='png', compress_level=0)

image_from_bytes = toga.Image(data=buffer.getvalue())
imageview_from_bytes = toga.ImageView(image_from_bytes)
imageview_from_bytes.style.update(height=72)
box.add(imageview_from_bytes)

# image from remote URL
# no style parameters - we let Pack determine how to allocate
# the space
Expand Down
1 change: 1 addition & 0 deletions examples/imageview/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ description = "A testing app"
sources = ['imageview']
requires = [
'../../src/core',
"Pillow",
]


Expand Down
4 changes: 3 additions & 1 deletion src/android/toga_android/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@


class Image:
def __init__(self, interface, path=None, url=None):
def __init__(self, interface, path=None, url=None, data=None):
self.interface = interface
self.path = path
self.url = url
Expand All @@ -12,3 +12,5 @@ def __init__(self, interface, path=None, url=None):
elif url:
# Android BitmapFactory nor ImageView provide a convenient async way to fetch images by URL
self.native = None
elif data:
self.native = BitmapFactory.decodeByteArray(data, 0, len(data))
7 changes: 6 additions & 1 deletion src/cocoa/toga_cocoa/images.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from toga_cocoa.libs import NSURL, NSImage
from toga_cocoa.libs.foundation import NSData


class Image:
def __init__(self, interface, path=None, url=None):
def __init__(self, interface, path=None, url=None, data=None):
self.interface = interface
self.path = path
self.url = url
Expand All @@ -13,3 +14,7 @@ def __init__(self, interface, path=None, url=None):
self.native = NSImage.alloc().initByReferencingURL(
NSURL.URLWithString_(url)
)
elif data:
self.native = NSImage.alloc().initWithData(
NSData.dataWithBytes(data, length=len(data))
)
4 changes: 4 additions & 0 deletions src/cocoa/toga_cocoa/libs/foundation.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
NSBundle.declare_class_property('mainBundle')
NSBundle.declare_property('bundlePath')

######################################################################
# NSData.h
NSData = ObjCClass('NSData')

######################################################################
# NSFileWrapper.h
NSFileWrapper = ObjCClass('NSFileWrapper')
Expand Down
17 changes: 17 additions & 0 deletions src/core/tests/test_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,20 @@ def test_str_file_image_path(self):

def test_url_image_path(self):
self.assertEqual(self.url_image.path, self.url_path)

def test_bytes_image(self):
data = bytes([1])
bytes_image = toga.Image(data=data)
bytes_image.bind(factory=toga_dummy.factory)
self.assertEqual(bytes_image._impl.interface, bytes_image)
self.assertActionPerformedWith(bytes_image, 'load image data', data=data)

def test_not_enough_arguments(self):
with self.assertRaises(ValueError):
toga.Image(None)

def test_too_many_arguments(self):
path = '/image.png'
data = bytes([1])
with self.assertRaises(ValueError):
toga.Image(path=path, data=data)
29 changes: 21 additions & 8 deletions src/core/toga/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,25 @@ class Image:
:param path: Path to the image. Allowed values can be local file
(relative or absolute path) or URL (HTTP or HTTPS). Relative paths
will be interpreted relative to the application module directory.
:param data: A bytes object with the contents of an image in a supported
format.
"""
def __init__(self, path):
if isinstance(path, pathlib.Path):
self.path = path
elif path.startswith('http://') or path.startswith('https://'):
self.path = path
def __init__(self, path=None, *, data=None):
if path is None and data is None:
raise ValueError('Either path or data must be set.')
if path is not None and data is not None:
raise ValueError('Only either path or data can be set.')

if path:
if isinstance(path, pathlib.Path):
self.path = path
elif path.startswith('http://') or path.startswith('https://'):
self.path = path
else:
self.path = pathlib.Path(path)
else:
self.path = pathlib.Path(path)
self.path = None
self.data = data

# Resource is late bound.
self._impl = None
Expand All @@ -24,14 +35,16 @@ def bind(self, factory):
"""
Bind the Image to a factory.
Creates the underlying platform implemenation of the Image. Raises
Creates the underlying platform implementation of the Image. Raises
FileNotFoundError if the path is a non-existent local file.
:param factory: The platform factory to bind to.
:returns: The platform implementation
"""
if self._impl is None:
if isinstance(self.path, pathlib.Path):
if self.data is not None:
self._impl = factory.Image(interface=self, data=self.data)
elif isinstance(self.path, pathlib.Path):
full_path = factory.paths.app / self.path
if not full_path.exists():
raise FileNotFoundError(
Expand Down
4 changes: 3 additions & 1 deletion src/dummy/toga_dummy/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@


class Image(LoggedObject):
def __init__(self, interface, path=None, url=None):
def __init__(self, interface, path=None, url=None, data=None):
super().__init__()
self.interface = interface
self.path = path
Expand All @@ -12,3 +12,5 @@ def __init__(self, interface, path=None, url=None):
self._action('load image file', path=path)
elif self.url:
self._action('load image url', url=url)
elif data:
self._action('load image data', data=data)
7 changes: 5 additions & 2 deletions src/gtk/toga_gtk/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@


class Image:
def __init__(self, interface, path=None, url=None):
def __init__(self, interface, path=None, url=None, data=None):
self.interface = interface
self.path = path
self.url = url

if path:
self.native = GdkPixbuf.Pixbuf.new_from_file(str(path))
else:
elif url:
request = Request(url, headers={'User-Agent': ''})
with urlopen(request) as result:
input_stream = Gio.MemoryInputStream.new_from_data(result.read(), None)
self.native = GdkPixbuf.Pixbuf.new_from_stream(input_stream, None)
elif data:
input_stream = Gio.MemoryInputStream.new_from_data(data, None)
self.native = GdkPixbuf.Pixbuf.new_from_stream(input_stream, None)
6 changes: 5 additions & 1 deletion src/iOS/toga_iOS/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@


class Image(object):
def __init__(self, interface, path=None, url=None):
def __init__(self, interface, path=None, url=None, data=None):
self.interface = interface
self.path = path
self.url = url
Expand All @@ -16,3 +16,7 @@ def __init__(self, interface, path=None, url=None):
NSURL.URLWithString_(path)
)
)
elif data:
self.native = UIImage.imageWithData_(
NSData.dataWithBytes(data, length=len(data))
)
7 changes: 5 additions & 2 deletions src/winforms/toga_winforms/images.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from toga_winforms.libs import WinImage
from toga_winforms.libs import WinImage, MemoryStream


class Image(object):
def __init__(self, interface, path=None, url=None):
def __init__(self, interface, path=None, url=None, data=None):
self.interface = interface
self.path = path
self.url = url
Expand All @@ -13,3 +13,6 @@ def __init__(self, interface, path=None, url=None):
# Windows loads URL images in the view,
# not as standalone resources
self.native = None
elif data:
stream = MemoryStream(data)
self.native = WinImage.FromStream(stream)
1 change: 1 addition & 0 deletions src/winforms/toga_winforms/libs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
Graphics,
GraphicsPath,
Matrix,
MemoryStream,
Pen,
Point,
PointF,
Expand Down
2 changes: 1 addition & 1 deletion src/winforms/toga_winforms/libs/winforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@

from System.Drawing.Text import PrivateFontCollection # noqa: F401, E402

from System.IO import FileNotFoundException # noqa: F401, E402
from System.IO import FileNotFoundException, MemoryStream # noqa: F401, E402
from System.Runtime.InteropServices import ExternalException # noqa: F401, E402

from System.Threading.Tasks import Task, TaskScheduler # noqa: F401, E402
Expand Down

0 comments on commit 9a292f8

Please sign in to comment.