-
-
Notifications
You must be signed in to change notification settings - Fork 671
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
Adding support for Pillow Image Object in toga #2154
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few comments inline, in addition to the ones on the main ticket about API design.
Also - this PR will need tests, plus a changenote.
I will resolve them as you have said. Sorry for the silly mistakes.. Indeed this is my first GitHub contribution. |
How to test this features? And how to add a changenote? |
See the contribution guide for details on writing a change note. As for tests - as the changes are all in the core API, you only need to add tests for the core API. The tests are all Pytest test cases; you need to add tests that validate the new code paths you've added - that is, instantiating Image instances using PIL objects. You'll be looking in the following files: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A good first pass at tests; but a few notes inline.
def test_as_image_format_is_none(): | ||
img = toga.Image("resources/toga.png") | ||
imageview = toga.ImageView(image=img) | ||
img2 = imageview.as_image() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As with Image, this should be an argument error.
wrote tests for unified src Minor changes to code for supporting pillow Fixes beeware#2156
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some more suggestions inline.
I also strongly suggest you read the rest of the contribution guide I've linked previously - it contains details like how to run the test suite locally, and how to set up pre-commit so that code formatting issues will be corrected before pushing to CI.
if path is not None: | ||
if isinstance(path, Path): | ||
self.path = path | ||
else: | ||
self.path = Path(path) | ||
self.data = None | ||
else: | ||
self.path = None | ||
if data is not None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm fairly certain this leaves open a path toga.Image()
won't raise an error. The test suite should catch this... but the test suite isn't currently running, because of other problems with the test suite.
self.path = toga.App.app.paths.app / self.path | ||
if not self.path.is_file(): | ||
raise FileNotFoundError(f"Image file {self.path} does not exist") | ||
self._impl = self.factory.Image(interface=self, path=self.path) | ||
|
||
|
||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should be 2 lines between classes. This is picked up automatically when you install pre-commit.
core/src/toga/images.py
Outdated
if format==None: | ||
return self | ||
elif isinstance(format, type(self)): | ||
return self |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not clear to me why as_format()
shouldn't require a single argument - the format you want the image in.
It's also not clear to me why as_format(toga.Image)
is something anyone would ever invoke... on toga.Image.
if format == None: | ||
return Image(data=self._impl.get_image_data()) | ||
elif isinstance(format, toga.Image): | ||
return Image(data=self._impl.get_image_data()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This effectively means that format has a default value of toga.Image
. This simplifies the type description, and simplifies the implementation because there's no duplicated code path.
@@ -63,7 +70,7 @@ def rehint_imageview(image, style, scale=1): | |||
class ImageView(Widget): | |||
def __init__( | |||
self, | |||
image: Image | Path | str | None = None, | |||
image: Image | Path | str | Any | None = None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you're using Any, there's no need to specify anything else, because any type is a subset of Any.
core/src/toga/widgets/imageview.py
Outdated
@@ -79,6 +86,8 @@ def __init__( | |||
# Prime the image attribute | |||
self._image = None | |||
self._impl = self.factory.ImageView(interface=self) | |||
if PIL_Image!=None and PIL_Image.isImageType(image): | |||
image = Image(image) #PIL.Image.Image -> toga.Image |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I said in my previous review - this isn't needed. imageview.image = image
calls the setter, and if the argument isn't None or a toga.Image(), a toga.Image is constructed. We can completely defer all type checks to the toga.Image() constructor.
if format==None: | ||
return self.image | ||
elif isinstance(format, Image): | ||
return self.image |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If two branches have the same implementation, perhaps consider whether an or
is needed.
Closing; superseded by #2231 |
Added a new parameter called
src
in toga.Image that takes any supported image sourcessupported image sources: str, pathlib.Path, bytes, BytesIO, BufferedReader, PIL.Image.Image
type of the src is detected using
isinstance
. Then it is converted to eitherpath
ordata
. Rest of the code is untouched.Only an overhead is added for the sake of users.
Added support for Pillow Image Object in toga.Image.
toga.Image can take PIL.Image.Image as its source. Internally PIL.Image.Image is saved to a BytesIO using its
save
method.then from the BytesIO, bytes are extracted and passed to data parameter.
Added support from PIL.Image in toga.ImageView
a type checking occurs in init which on detect of PIL.Image.Image, converts it to toga.Image by passing it to the same.
implemented toga.Image.as_format
has a parameter
format
which when not set(i.e. None) returns the same toga.Image object from which it is being called. But when format is set to PIL.Image.Image, it will convert the image by following mean:save
method of self.implemented toga.ImageView.as_image
has a parameter called
format
. which if None returns toga.Image else if set to PIL_Image.Image, will return Pillow object. Internally calls toga.Image.as_formatmodified Canvas.as_image to support PIL.Image return
has parameter format which if none returns toga.Image. if set to PIL_Image.Image return an object of same but this time uses PIL_Image.open(BytesIO) unlike toga.Image.as_format which uses saving temp file to disk. The BytesIO for PIL_Image.open is constructed from self._impl.get_image_data which returns raw bytes.
toga.Image can take any supported image sources as its first parameter #2156
Allow toga to work with Pillow image files #2142
Fixes #2142, Fixes #2156
PR Checklist: